feat(newsfeed): Improved UI flow for the course newsfeed (#791)
* feat(newsfeed): improved the newsfeed ui * feat(newsfeed): updated the ui * feat(newsfeed): some improvements * feat(newsfeed): cleanup * chore(newsfeed): added translations * fix(newsfeed): fixing comments and reviews from greptile and coderabbit * fix(newsfeed): fixing comments and reviews from greptile and coderabbit, improved replies to a reply * fix(newsfeed): fixing reviews * fix(ui): expose cancelLabel prop in comment-tree-input and pass localized text --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
parent
88c4cf03b3
commit
a1c4ee720f
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE "course_newsfeed_comment" ADD COLUMN "parent_id" bigint;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "course_newsfeed_comment" ADD CONSTRAINT "course_newsfeed_comment_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "public"."course_newsfeed_comment"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "course_newsfeed_comment_parent_id_idx" ON "course_newsfeed_comment" USING btree ("parent_id");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,13 @@
|
||||
"when": 1783938329367,
|
||||
"tag": "0004_organizationmember_email_notifications",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1785228000000,
|
||||
"tag": "0005_add_parent_id_to_course_newsfeed_comment",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as schema from '@db/schema';
|
||||
|
||||
import type { TCourseNewsfeed, TCourseNewsfeedComment, TNewCourseNewsfeed, TNewCourseNewsfeedComment } from '@db/types';
|
||||
import { and, db, desc, eq, lt, ne, sql } from '@db/drizzle';
|
||||
import { and, db, desc, eq, isNull, lt, ne, sql } from '@db/drizzle';
|
||||
import { ROLE } from '@cio/utils/constants';
|
||||
|
||||
/**
|
||||
@@ -221,36 +221,50 @@ export async function getNewsfeedCommentsByFeedId(feedId: string): Promise<TCour
|
||||
/**
|
||||
* Gets paginated comments for a newsfeed item with author profile
|
||||
* @param feedId Newsfeed ID
|
||||
* @param options Pagination options (cursor, limit)
|
||||
* @param options Pagination options (parentId, cursor, limit)
|
||||
* @returns Object with comments array, total count, hasMore flag, and nextCursor
|
||||
*/
|
||||
export async function getNewsfeedCommentsByFeedIdPaginated(
|
||||
feedId: string,
|
||||
options: { cursor?: string; limit: number }
|
||||
options: { parentId?: number; cursor?: string; limit: number }
|
||||
) {
|
||||
try {
|
||||
const { cursor, limit } = options;
|
||||
const { parentId, cursor, limit } = options;
|
||||
|
||||
// Build base parent-scoped conditions
|
||||
const parentCondition =
|
||||
parentId !== undefined
|
||||
? eq(schema.courseNewsfeedComment.parentId, parentId)
|
||||
: isNull(schema.courseNewsfeedComment.parentId);
|
||||
|
||||
const baseWhereConditions = [eq(schema.courseNewsfeedComment.courseNewsfeedId, feedId), parentCondition];
|
||||
|
||||
const whereConditions = [...baseWhereConditions];
|
||||
|
||||
// Build where conditions
|
||||
const whereConditions = [eq(schema.courseNewsfeedComment.courseNewsfeedId, feedId)];
|
||||
if (cursor) {
|
||||
// Cursor is the last comment ID, fetch comments created after it
|
||||
whereConditions.push(lt(schema.courseNewsfeedComment.id, Number(cursor)));
|
||||
}
|
||||
|
||||
// Get total count
|
||||
const totalCountResult = await db
|
||||
.select({ count: sql<number>`count(*)`.as('count') })
|
||||
.from(schema.courseNewsfeedComment)
|
||||
.where(eq(schema.courseNewsfeedComment.courseNewsfeedId, feedId));
|
||||
.where(and(...baseWhereConditions));
|
||||
const totalCount = Number(totalCountResult[0]?.count || 0);
|
||||
|
||||
// Fetch comments with author profile (limit + 1 to check if there are more)
|
||||
// Fetch comments with author profile & reply count
|
||||
const comments = await db
|
||||
.select({
|
||||
comment: schema.courseNewsfeedComment,
|
||||
groupmember: schema.groupmember,
|
||||
profile: schema.profile
|
||||
profile: schema.profile,
|
||||
replyCount: sql<number>`
|
||||
COALESCE(
|
||||
(SELECT COUNT(*)::int
|
||||
FROM ${schema.courseNewsfeedComment} c2
|
||||
WHERE c2.parent_id = ${schema.courseNewsfeedComment.id}),
|
||||
0
|
||||
)
|
||||
`.as('replyCount')
|
||||
})
|
||||
.from(schema.courseNewsfeedComment)
|
||||
.leftJoin(schema.groupmember, eq(schema.courseNewsfeedComment.authorId, schema.groupmember.id))
|
||||
@@ -259,11 +273,9 @@ export async function getNewsfeedCommentsByFeedIdPaginated(
|
||||
.orderBy(desc(schema.courseNewsfeedComment.createdAt))
|
||||
.limit(limit + 1);
|
||||
|
||||
// Check if there are more comments
|
||||
const hasMore = comments.length > limit;
|
||||
const items = comments.slice(0, limit);
|
||||
|
||||
// Get next cursor (ID of the oldest comment in this batch - for loading older comments)
|
||||
const nextCursor = hasMore && items.length > 0 ? String(items[items.length - 1].comment.id) : null;
|
||||
|
||||
return {
|
||||
@@ -272,7 +284,8 @@ export async function getNewsfeedCommentsByFeedIdPaginated(
|
||||
authorProfileId: row.profile?.id || null,
|
||||
authorFullname: row.profile?.fullname || null,
|
||||
authorUsername: row.profile?.username || null,
|
||||
authorAvatarUrl: row.profile?.avatarUrl || null
|
||||
authorAvatarUrl: row.profile?.avatarUrl || null,
|
||||
replyCount: Number(row.replyCount || 0)
|
||||
})),
|
||||
totalCount,
|
||||
hasMore,
|
||||
@@ -343,6 +356,27 @@ export async function deleteNewsfeedComment(commentId: number): Promise<TCourseN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single newsfeed comment by ID
|
||||
* @param commentId Comment ID
|
||||
* @returns Comment or null
|
||||
*/
|
||||
export async function getNewsfeedCommentById(commentId: number): Promise<TCourseNewsfeedComment | null> {
|
||||
try {
|
||||
const [comment] = await db
|
||||
.select()
|
||||
.from(schema.courseNewsfeedComment)
|
||||
.where(eq(schema.courseNewsfeedComment.id, commentId))
|
||||
.limit(1);
|
||||
return comment || null;
|
||||
} catch (error) {
|
||||
console.error('getNewsfeedCommentById error:', error);
|
||||
throw new Error(
|
||||
`Failed to get newsfeed comment by ID "${commentId}": ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets comment author and course ID for authorization checks
|
||||
* @param commentId Comment ID
|
||||
@@ -489,7 +523,8 @@ function getCommentCount() {
|
||||
COALESCE(
|
||||
(SELECT COUNT(*)::int
|
||||
FROM ${schema.courseNewsfeedComment}
|
||||
WHERE ${eq(schema.courseNewsfeedComment.courseNewsfeedId, schema.courseNewsfeed.id)}),
|
||||
WHERE ${eq(schema.courseNewsfeedComment.courseNewsfeedId, schema.courseNewsfeed.id)}
|
||||
AND ${isNull(schema.courseNewsfeedComment.parentId)}),
|
||||
0
|
||||
)
|
||||
`.as('commentCount');
|
||||
|
||||
@@ -1692,7 +1692,8 @@ export const courseNewsfeedComment = pgTable(
|
||||
minValue: 1,
|
||||
cache: 1
|
||||
}),
|
||||
courseNewsfeedId: uuid('course_newsfeed_id')
|
||||
courseNewsfeedId: uuid('course_newsfeed_id'),
|
||||
parentId: bigint('parent_id', { mode: 'number' })
|
||||
},
|
||||
(table) => [
|
||||
foreignKey({
|
||||
@@ -1704,7 +1705,13 @@ export const courseNewsfeedComment = pgTable(
|
||||
columns: [table.courseNewsfeedId],
|
||||
foreignColumns: [courseNewsfeed.id],
|
||||
name: 'course_newsfeed_comment_course_newsfeed_id_fkey'
|
||||
})
|
||||
}),
|
||||
foreignKey({
|
||||
columns: [table.parentId],
|
||||
foreignColumns: [table.id],
|
||||
name: 'course_newsfeed_comment_parent_id_fkey'
|
||||
}).onDelete('cascade'),
|
||||
index('course_newsfeed_comment_parent_id_idx').on(table.parentId)
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user