Skip to content

Commit 9379aa6

Browse files
authored
Merge pull request #460 from CodebilityDev/miguel/feed-improvement-2
Miguel/feed improvement 2
2 parents 57bc6af + decb61d commit 9379aa6

15 files changed

Lines changed: 303 additions & 184 deletions

‎apps/codebility/app/home/feeds/IMPROVEMENTS.md‎

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ This document outlines potential improvements for the feeds feature based on cod
44

55
## Performance & Optimization
66

7-
1. **Implement infinite scroll** instead of traditional pagination
7+
<!-- 1. **Implement infinite scroll** instead of traditional pagination
88
- Better UX for feed browsing
99
- Reduces cognitive load on users
10-
- More engaging scrolling experience
10+
- More engaging scrolling experience -->
1111

1212
2. **Add optimistic updates** for upvotes/comments
1313
- Instant UI feedback before server response
@@ -19,17 +19,12 @@ This document outlines potential improvements for the feeds feature based on cod
1919
- Automatic background refetching
2020
- Built-in stale-while-revalidate pattern
2121

22-
4. **Add real-time updates** using Supabase subscriptions
23-
- New posts appear automatically without manual refresh
24-
- Live upvote/comment count updates
25-
- Better collaboration experience
26-
27-
5. **Optimize image loading**
22+
4. **Optimize image loading**
2823
- Add blur placeholders (Next.js Image component supports this)
2924
- Lazy loading for images below the fold
3025
- Progressive image loading
3126

32-
6. **Memoize filtered posts computation**
27+
5. **Memoize filtered posts computation**
3328
- Already using `useMemo` in Feed.tsx:25-41
3429
- Could optimize further with better dependency keys
3530
- Consider moving heavy filtering to server-side
@@ -41,11 +36,11 @@ This document outlines potential improvements for the feeds feature based on cod
4136
- Multiple categories per post
4237
- Visual category badges
4338

44-
8. **Sort options**
39+
<!-- 8. **Sort options**
4540
- Newest first (current default)
4641
- Most upvoted
4742
- Trending (combination of recent + upvotes)
48-
- Most commented
43+
- Most commented -->
4944

5045
9. **Bookmark/save posts**
5146
- Let users save posts for later reading
@@ -57,10 +52,10 @@ This document outlines potential improvements for the feeds feature based on cod
5752
- Show content excerpt with "Read more..."
5853
- Better preview of post content
5954

60-
11. **Comment count display** on PostCard
55+
<!-- 11. **Comment count display** on PostCard
6156
- Show engagement metrics at a glance
6257
- Currently only shows upvote count
63-
- Display next to upvote button
58+
- Display next to upvote button -->
6459

6560
12. **Share functionality**
6661
- Copy link to clipboard
@@ -72,22 +67,17 @@ This document outlines potential improvements for the feeds feature based on cod
7267
- Notify mentioned users
7368
- Highlight mentions in text
7469

75-
14. **Image gallery view** for posts with multiple images
76-
- Currently supports single thumbnail
77-
- Carousel/gallery for multiple images
78-
- Lightbox for full-size viewing
79-
80-
15. **Draft posts**
70+
14. **Draft posts**
8171
- Save work in progress before publishing
8272
- Auto-save functionality
8373
- Resume editing later
8474

8575
## Code Quality & Architecture
8676

87-
16. **Fix hardcoded admin check** (page.tsx:13)
77+
<!-- 16. **Fix hardcoded admin check** (page.tsx:13)
8878
- Currently: `const [isAdmin, setIsAdmin] = useState(true);`
8979
- Should initialize as `false`
90-
- Security risk if not properly validated
80+
- Security risk if not properly validated -->
9181

9282
17. **Consolidate duplicate user role fetching**
9383
- Used in page.tsx:20-27 and PostView.tsx:48-52
@@ -110,15 +100,15 @@ This document outlines potential improvements for the feeds feature based on cod
110100
- Graceful degradation
111101
- User-friendly error messages
112102

113-
21. **Loading skeletons** for individual post cards
103+
<!-- 21. **Loading skeletons** for individual post cards
114104
- Currently only in PostView.tsx:110-115
115105
- Add to Feed.tsx while posts are loading
116-
- Better loading states
106+
- Better loading states -->
117107

118-
22. **Extract magic numbers** to constants
108+
<!-- 22. **Extract magic numbers** to constants
119109
- `postsPerPage: 6` (Feed.tsx:43)
120110
- Debounce delay `500ms` (page.tsx:32)
121-
- Create `constants.ts` file
111+
- Create `constants.ts` file -->
122112

123113
23. **Consistent async/await error handling**
124114
- Some functions use try/catch, others don't
@@ -234,29 +224,29 @@ This document outlines potential improvements for the feeds feature based on cod
234224

235225
These improvements can be implemented quickly and provide immediate value:
236226

237-
- ✅ **Fix the `isAdmin` initialization bug** (page.tsx:13)
227+
<!-- - ✅ **Fix the `isAdmin` initialization bug** (page.tsx:13)
238228
- Change `useState(true)` to `useState(false)`
239229
- 1-line fix, important security improvement
240230
241231
- ✅ **Add comment count to PostCard component**
242232
- Query comment count in getPosts
243233
- Display next to upvote count
244-
- ~20 lines of code
234+
- ~20 lines of code -->
245235

246-
- ✅ **Extract constants**
236+
<!-- - ✅ **Extract constants**
247237
- Create `_constants/index.ts`
248238
- Move magic numbers: `POSTS_PER_PAGE = 6`, `SEARCH_DEBOUNCE_MS = 500`
249-
- Better maintainability
239+
- Better maintainability -->
250240

251-
- ✅ **Add loading skeleton for post cards**
241+
<!-- - ✅ **Add loading skeleton for post cards**
252242
- Reuse existing Skeleton component
253243
- Show while `fetchPosts()` is loading
254-
- ~15 lines of code
244+
- ~15 lines of code -->
255245

256-
- ✅ **Add empty state when no posts match search**
246+
<!-- - ✅ **Add empty state when no posts match search**
257247
- Simple conditional in Feed.tsx
258248
- Better UX than empty grid
259-
- ~10 lines of code
249+
- ~10 lines of code -->
260250

261251
## File References
262252

‎apps/codebility/app/home/feeds/_components/Feed.tsx‎

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import { useEffect, useMemo, useRef, useState } from "react";
44
import { useFeedsStore } from "@/store/feeds-store";
55

6+
import { POSTS_PER_PAGE } from "../_constants";
67
import Post from "./PostCard";
8+
import PostCardSkeleton from "./PostCardSkeleton";
79

810
interface FeedProp {
911
isAdmin: boolean;
1012
searchQuery?: string;
11-
sortField: "title" | "date" | "upvotes";
13+
sortField: "title" | "date" | "upvotes" | "comments";
1214
sortOrder: "asc" | "desc";
1315
}
1416

@@ -20,13 +22,14 @@ export default function Feed({
2022
}: FeedProp) {
2123
const posts = useFeedsStore((state) => state.posts);
2224
const fetchPosts = useFeedsStore((state) => state.fetchPosts);
25+
const isFetchingPosts = useFeedsStore((state) => state.isFetchingPosts);
2326

24-
const [visibleCount, setVisibleCount] = useState(6);
27+
const [visibleCount, setVisibleCount] = useState(POSTS_PER_PAGE);
2528
const loaderRef = useRef<HTMLDivElement | null>(null);
2629

2730
useEffect(() => {
2831
fetchPosts();
29-
}, [fetchPosts]);
32+
}, []);
3033

3134
// Filter posts
3235
const filteredPosts = useMemo(() => {
@@ -64,6 +67,10 @@ export default function Feed({
6467
aVal = a.upvote_count ?? 0;
6568
bVal = b.upvote_count ?? 0;
6669
break;
70+
case "comments":
71+
aVal = a.comment_count ?? 0;
72+
bVal = b.comment_count ?? 0;
73+
break;
6774
}
6875

6976
if (aVal < bVal) return sortOrder === "asc" ? -1 : 1;
@@ -76,7 +83,7 @@ export default function Feed({
7683
const visiblePosts = sortedPosts.slice(0, visibleCount);
7784

7885
useEffect(() => {
79-
setVisibleCount(6);
86+
setVisibleCount(POSTS_PER_PAGE);
8087
}, [searchQuery]);
8188

8289
// Infinite scroll
@@ -85,7 +92,9 @@ export default function Feed({
8592
(entries) => {
8693
const entry = entries[0];
8794
if (entry?.isIntersecting) {
88-
setVisibleCount((prev) => Math.min(prev + 6, sortedPosts.length));
95+
setVisibleCount((prev) =>
96+
Math.min(prev + POSTS_PER_PAGE, sortedPosts.length),
97+
);
8998
}
9099
},
91100
{ threshold: 1 },
@@ -107,18 +116,22 @@ export default function Feed({
107116
<>
108117
{/* Posts grid */}
109118
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
110-
{visiblePosts.map((post) => (
111-
<Post
112-
key={post.id}
113-
post={post}
114-
isAdmin={isAdmin}
115-
onDelete={handleDeletePost}
116-
/>
117-
))}
119+
{isFetchingPosts
120+
? Array.from({ length: 6 }).map((_, i) => (
121+
<PostCardSkeleton key={i} />
122+
))
123+
: visiblePosts.map((post) => (
124+
<Post
125+
key={post.id}
126+
post={post}
127+
isAdmin={isAdmin}
128+
onDelete={handleDeletePost}
129+
/>
130+
))}
118131
</div>
119132

120133
{/* Loader */}
121-
{visibleCount < sortedPosts.length && (
134+
{!isFetchingPosts && visibleCount < sortedPosts.length && (
122135
<div
123136
ref={loaderRef}
124137
className="mt-6 flex justify-center text-sm text-gray-500"
@@ -131,7 +144,7 @@ export default function Feed({
131144
)}
132145

133146
{/* Empty state */}
134-
{sortedPosts.length === 0 && (
147+
{!isFetchingPosts && sortedPosts.length === 0 && (
135148
<div className="mt-8 text-center text-gray-500">No posts found.</div>
136149
)}
137150
</>

‎apps/codebility/app/home/feeds/_components/PostCard.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { Box } from "@/components/shared/dashboard";
66
import { defaultAvatar } from "@/public/assets/images";
77
import { useUserStore } from "@/store/codev-store";
88
import { format } from "date-fns";
9-
import { ArrowBigUp } from "lucide-react";
9+
import { ArrowBigUp, MessageSquare } from "lucide-react";
1010
import toast from "react-hot-toast";
1111

1212
import { deletePost } from "../_services/action";
1313
import { PostType } from "../_services/query";
1414
import { DeleteDialog } from "./DeleteDialog";
15+
import PostCommentCount from "./PostCommentCount";
1516
import PostUpvote from "./PostUpvote";
1617

1718
interface PostProps {
@@ -124,6 +125,8 @@ export default function Post({ post, isAdmin, onDelete }: PostProps) {
124125

125126
<div className="mx-4 mt-4 flex items-center space-x-4 text-gray-600 dark:text-gray-400">
126127
<PostUpvote post={post} />
128+
129+
<PostCommentCount post={post} />
127130
</div>
128131
</Box>
129132
<FeedPostModal
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default function PostCardSkeleton() {
2+
return (
3+
<div className="animate-pulse rounded-lg border bg-white/70 p-4 shadow-lg backdrop-blur-sm dark:bg-gray-800/70">
4+
{/* Image */}
5+
<div className="h-40 w-full rounded-md bg-gray-200 dark:bg-gray-700" />
6+
7+
{/* Title */}
8+
<div className="mt-4 h-4 w-3/4 rounded bg-gray-200 dark:bg-gray-700" />
9+
10+
{/* Subtitle */}
11+
<div className="mt-2 h-4 w-1/2 rounded bg-gray-200 dark:bg-gray-700" />
12+
13+
{/* Footer badges */}
14+
<div className="mt-4 flex gap-3">
15+
<div className="h-4 w-10 rounded bg-gray-200 dark:bg-gray-700" />
16+
<div className="h-4 w-10 rounded bg-gray-200 dark:bg-gray-700" />
17+
</div>
18+
</div>
19+
);
20+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useEffect, useState } from "react";
2+
import { useFeedsStore } from "@/store/feeds-store";
3+
import { MessageSquare } from "lucide-react";
4+
5+
import { PostType } from "../_services/query";
6+
7+
interface PostCommentCountProps {
8+
post: PostType;
9+
}
10+
11+
export default function PostCommentCount({ post }: PostCommentCountProps) {
12+
const [count, setCount] = useState(post.comment_count ?? 0);
13+
14+
// Update local state whenever the post prop changes
15+
useEffect(() => {
16+
setCount(post.comment_count ?? 0);
17+
}, [post]);
18+
19+
return (
20+
<div className="flex items-center space-x-1">
21+
<MessageSquare size={20} className="text-gray-600 dark:text-gray-400" />
22+
<span>{count}</span>
23+
</div>
24+
);
25+
}

‎apps/codebility/app/home/feeds/_components/PostUpvote.tsx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,17 @@ export default function PostUpvote({ post }: PostUpvoteProps) {
6363

6464
if (!isUpvoted) {
6565
const postUpvote = await AddPostUpvote(post.id, user.id);
66+
67+
useFeedsStore.getState().updatePost(post.id, {
68+
upvote_count: post.comment_count! + 1,
69+
});
6670
} else {
6771
await removePostUpvote(post.id, user.id);
68-
}
6972

70-
await fetchPosts();
73+
useFeedsStore.getState().updatePost(post.id, {
74+
upvote_count: post.comment_count! - 1,
75+
});
76+
}
7177
}
7278
};
7379

0 commit comments

Comments
 (0)