Skip to content

Conversation

simon0x1800
Copy link
Collaborator

@simon0x1800 simon0x1800 commented Sep 15, 2025

Description:

  • Removed all sorting and filtering UI from PostFilters.tsx, leaving only the search bar.
  • Updated PostsList.tsx to render only the search bar via PostFilters.
  • Cleaned up unused props and imports for maintainability.
  • Refactored code to focus on a minimal, search-centric interface as requested.

Summary by CodeRabbit

  • New Features

    • New compact post gallery and single-post card with sentiment badges, colored sentiment bar, source icons/counts, and category/subcategory tag chips with “+N more” overflow.
    • Tag renderer added to present categories and subcategories consistently.
  • Refactor

    • Cards render multiple posts by default (can render a single); built-in mock data used when none provided.
    • Filters trimmed to a search-only input; sorting/other filters and previous loading/empty-state UI removed.

Copy link

coderabbitai bot commented Sep 15, 2025

Walkthrough

Replaces Prisma-backed PostCard with data-driven PostData models and mock data; adds SinglePostCard and PostsGallery, introduces TagRenderer, and reduces PostFilters to a search-only input. PostsList now renders PostFilters and PostsGallery; previous sorting, filtering UI and per-post rendering/infinite-scroll are removed.

Changes

Cohort / File(s) Summary of Changes
Post card / gallery refactor
src/components/PostComponents/PostCard.tsx, src/components/PostComponents/PostGallery.tsx
Introduces PostData-driven UI and mockPostsData. Adds/exported SinglePostCard({ post }: { post: PostData }). PostCard API changed to accept { posts?: PostData[]; showMultiple?: boolean } and now renders multiple cards by default. PostsGallery (new) renders a horizontal gallery of SinglePostCard. Sentiment badge, sentimentBar, explicit sources, and compact Card layout replace prior Prisma-based rendering and old icon logic.
Tag renderer
src/components/PostComponents/TagRenderer.tsx
New TagRenderer component combining categories and optional subcategories into up-to-five visible tag pills with a “+N more” overflow pill; styles differ for category vs subcategory.
Filters simplification
src/components/PostComponents/PostFilters.tsx
PostFiltersProps reduced to search and onSearchChange; removed sort/order/sentiment/source props and UI. Component now renders only the search input (minor import formatting).
Posts list update
src/components/PostComponents/PostsList.tsx
Replaced detailed posts rendering with simplified layout that renders PostFilters (search-only) and PostGallery/PostCard gallery. Existing fetch state (fetchPosts, loading, hasMore, etc.) remains but is no longer connected to visible post rendering.
Exports / public API surface
src/components/PostComponents/*.tsx
Added exports: SinglePostCard, PostsGallery, and TagRenderer; changed default PostCard props shape. Some icons and old sentiment/icon imports removed across files.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant PostsList
  participant PostFilters
  participant PostsGallery
  participant SinglePostCard
  Note over PostsList,PostsGallery: New gallery-driven render flow (mock/default data)

  User->>PostsList: Open posts page
  PostsList->>PostFilters: Render({ search, onSearchChange })
  User->>PostFilters: Type search
  PostFilters-->>PostsList: onSearchChange(query)

  PostsList->>PostsGallery: Render({ posts = mockPostsData })
  PostsGallery->>SinglePostCard: map(posts)
  loop per post
    SinglePostCard-->>User: Render card (title, desc, sentiment badge, bar, sources, tags)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • tasin2610

Poem

A rabbit finds the cards anew,
Tags like clover, three by two.
Sentiment bars in tidy rows,
Mocked little posts where curiosity grows.
I hop, I cheer — the gallery shows. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly summarizes the primary change—refactoring PostCard and PostFilters for an improved UI—and matches the diffs which show PostCard API/behavior changes, removal of sorting/filter controls from PostFilters, and related UI simplifications; it is concise, on-topic, and free of noise.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/postcards

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/PostComponents/PostCard.tsx (1)

212-229: Guard empty lists and remove implicit mock fallback.

posts[0] can throw when posts is empty. Also, defaulting to mockPostsData hides real data. Make posts required (or return an empty state) and use stable keys.

-export default function PostCard({
-  posts = mockPostsData,
-  showMultiple = true,
-}: PostCardProps) {
-  if (showMultiple) {
-    return (
-      <div className="flex flex-row justify-center gap-6 p-2">
-        {posts.map((post, index) => (
-          <SinglePostCard key={index} post={post} />
-        ))}
-      </div>
-    );
-  }
-
-  // Show only the first card if showMultiple is false
-  return <SinglePostCard post={posts[0]} />;
-}
+export default function PostCard({ posts = [], showMultiple = true }: PostCardProps) {
+  if (!posts.length) {
+    return null; // or a skeleton/empty-state component
+  }
+  if (showMultiple) {
+    return (
+      <div className="flex flex-row justify-center gap-6 p-2">
+        {posts.map((post) => (
+          <SinglePostCard key={(post as any).id ?? post.title} post={post} />
+        ))}
+      </div>
+    );
+  }
+  return <SinglePostCard post={posts[0]} />;
+}
🧹 Nitpick comments (10)
src/components/PostComponents/PostCard.tsx (4)

9-14: Data should be JSON-friendly: move icon/color out of the data model.

Embedding ReactNode in data prevents server/transport usage. Keep Source serializable (name, count), derive icon/color at render.

If you want, I can provide a small helper like renderSourcePill(name, count) that maps to icon/color without mutating the data model.


124-126: Tailwind class likely invalid: w-54.

Tailwind’s default scale has w-52 and w-56; w-54 won’t apply unless custom-configured. Use a valid token or arbitrary value.

-    <Card className="w-54 h-60 font-sans">
+    <Card className="w-56 h-60 font-sans">  {/* or w-[216px] */}

168-185: Source.color is unused; either remove it or use it consistently.

You’re hardcoding pill colors by name. If you keep color in the model, apply it in the className; otherwise drop it from the type and mock data.


219-221: Prefer stable keys over array indices.

Use post.id or another stable identifier to prevent reconciliation issues on reorders.

See the diff on Lines 212-229 where the key uses post.id ?? post.title.

src/components/PostComponents/PostsList.tsx (3)

2-9: Remove unused imports (SentimentBar, Spinner) to avoid lint noise.

-import SentimentBar from "./SentimentBar";
-import { SortField, SortOrder } from "./PostSortSelect";
-import Spinner from "./Spinner";
+import { SortField, SortOrder } from "./PostSortSelect";

25-28: Debounce the search input to reduce unnecessary requests.

The current handler fires per keystroke; consider a 250–400ms debounce.

I can add a small useDebouncedValue hook if you want it in this PR.


29-73: Abort in-flight fetches on param changes to avoid race conditions.

Success of a slower previous request can overwrite newer results. Use AbortController per call and pass signal to fetch.

Happy to provide a minimal diff using AbortController and cleanup in useEffect.

src/components/PostComponents/PostFilters.tsx (3)

1-1: Remove unused Select imports.

-import { Select, SelectItem } from "@/components/ui/select";

3-3: Remove unused PostSortSelect and type imports.

-import { PostSortSelect, SortField, SortOrder } from "./PostSortSelect";

15-22: Add an accessible label to the search input.

-        <Input
+        <Input
+          aria-label="Search posts"
           value={search}
           onChange={onSearchChange}
           placeholder="Search posts..."
           className="w-full max-w-md shadow-md rounded-lg border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition"
         />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 860bd43 and f015145.

📒 Files selected for processing (3)
  • src/components/PostComponents/PostCard.tsx (1 hunks)
  • src/components/PostComponents/PostFilters.tsx (1 hunks)
  • src/components/PostComponents/PostsList.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/PostComponents/PostsList.tsx (2)
src/components/PostComponents/PostFilters.tsx (1)
  • PostFilters (10-26)
src/components/PostComponents/PostCard.tsx (1)
  • PostCard (212-228)
src/components/PostComponents/PostCard.tsx (1)
src/components/ui/card.tsx (4)
  • Card (91-91)
  • CardHeader (92-92)
  • CardTitle (94-94)
  • CardDescription (96-96)
🔇 Additional comments (1)
src/components/PostComponents/PostCard.tsx (1)

16-25: Export PostData (and include optional id) to enable typed adapters from API.
[ suggest_recommended_refactor ]

-type PostData = {
+export type PostData = {
+  id?: string;
   title: string;
   description: string;
   sentiment: "Bullish" | "Bearish" | "Neutral";
   sentimentBar: { bullish: number; neutral: number; bearish: number };
   postCount: number;
   sources: Source[];
   categories: string[];
   extraTags?: string;
 };

Comment on lines 130 to 133
<CardDescription className="text-[8px] text-gray-600 mb-2 line-clamp-2">
{post.description}
</CardDescription>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify line-clamp plugin is enabled.

line-clamp-2 requires the Tailwind plugin; otherwise truncation won’t apply.

Run this to confirm:


🏁 Script executed:

#!/bin/bash
fd -a 'tailwind.config.*' | xargs -I{} rg -n 'line-clamp' {}

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Finding tailwind config files ==="
fd -a 'tailwind.config*' --hidden --exclude node_modules || true
echo

echo "=== Searching repo for line-clamp plugin references and class usage ==="
rg -n --hidden --no-ignore-vcs --glob '!**/node_modules/**' "require\(\s*['\"]@tailwindcss/line-clamp['\"]\s*\)" || true
rg -n --hidden --no-ignore-vcs --glob '!**/node_modules/**' "@tailwindcss/line-clamp" || true
rg -n --hidden --no-ignore-vcs --glob '!**/node_modules/**' "lineClamp" || true
rg -n --hidden --no-ignore-vcs --glob '!**/node_modules/**' "line-clamp" || true
echo

echo "=== Showing contents of any tailwind config files found (first 300 lines) ==="
for f in $(fd -a 'tailwind.config*' --hidden --exclude node_modules || true); do
  echo "----- $f -----"
  sed -n '1,300p' "$f" || true
  echo
done

echo "=== Scanning package.json files for @tailwindcss/line-clamp and tailwindcss ==="
for p in $(fd -a 'package.json' --hidden --exclude node_modules || true); do
  echo "----- $p -----"
  jq -c '{dependencies, devDependencies}' "$p" 2>/dev/null || sed -n '1,240p' "$p"
  echo
done

Length of output: 1690


Enable @tailwindcss/line-clamp or remove the utility.

No tailwind.config.* found and repo root package.json lists tailwindcss but not @tailwindcss/line-clamp; src/components/PostComponents/PostCard.tsx (lines 130–133) uses class "line-clamp-2" — install the plugin and add require('@tailwindcss/line-clamp') to tailwind.config.js plugins, or replace with a CSS fallback.

🤖 Prompt for AI Agents
In src/components/PostComponents/PostCard.tsx around lines 130–133 the class
"line-clamp-2" is used but the repository either lacks a tailwind.config.js or
the @tailwindcss/line-clamp plugin is not installed/configured; fix by either
(A) installing the plugin (npm/yarn add -D @tailwindcss/line-clamp), ensure you
have a tailwind.config.js at repo root (create one if missing) and add
require('@tailwindcss/line-clamp') to the plugins array, then rebuild; or (B)
remove the "line-clamp-2" utility and replace with a CSS fallback on the
component such as overflow-hidden, display: -webkit-box, -webkit-line-clamp: 2
and -webkit-box-orient: vertical (added via a class or module stylesheet) to
achieve the same clamping without the plugin.

Comment on lines 99 to 105
<div className="mt-8">
<PostFilters
search={search}
onSearchChange={onChangeHandler}
/>
<PostCard/>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

UI ignores fetched posts and infinite scroll sentinel; search has no visible effect.

You fetch and paginate but render a PostCard with internal mock defaults. Also, no loadMoreRef element exists, so the observer never fires. Wire real data into PostCard and add the sentinel.

-    <div className="mt-8">
-      <PostFilters
-        search={search}
-        onSearchChange={onChangeHandler}
-      />
-      <PostCard/>
-    </div>
+    <div className="mt-8">
+      <PostFilters search={search} onSearchChange={onChangeHandler} />
+      {/* TODO: map API Post -> PostData. Placeholder passes empty array if not mapped yet */}
+      <PostCard posts={[]} showMultiple />
+      <div ref={loadMoreRef} />
+    </div>

Optionally, export PostData (see PostCard.tsx comment) and add an adapter:

// near fetchPosts or above return
import type { PostData } from "./PostCard";

function mapPostsToPostData(posts: Post[]): PostData[] {
  return posts.map((p) => ({
    id: (p as any).id,
    title: (p as any).title ?? "Untitled",
    description: (p as any).summary ?? (p as any).content ?? "",
    sentiment: ((p as any).sentiment ?? "Neutral") as PostData["sentiment"],
    sentimentBar: (p as any).sentimentBar ?? { bullish: 0, neutral: 100, bearish: 0 },
    postCount: (p as any).postCount ?? 0,
    sources: ((p as any).sources ?? []).map((s: any) => ({ name: s.name, count: s.count })),
    categories: (p as any).categories ?? [],
    extraTags: (p as any).extraTags,
  }));
}
🤖 Prompt for AI Agents
In src/components/PostComponents/PostsList.tsx around lines 99-105, the
component renders a single PostCard with mock/internal defaults and has no
sentinel for infinite-scroll, so fetched/paginated posts and the observer are
ignored; fix by importing/exporting the PostData type from PostCard, create a
mapPostsToPostData adapter (near the fetchPosts or above the return) to convert
fetched Post[] into PostData[], map the component's posts state through that
adapter, render a list of PostCard components (use post.id as key) passing the
mapped PostData props into each PostCard instead of a bare <PostCard/>, and add
a sentinel element (e.g., <div ref={loadMoreRef} />) after the list so the
intersection observer can fire to load more. Ensure onChangeHandler still wires
into PostFilters (already present) and remove any hardcoded defaults in PostCard
usage.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/components/PostComponents/PostCard.tsx (2)

114-116: line-clamp-2 requires plugin; add fallback or configure plugin.

Previous review flagged missing @tailwindcss/line-clamp. Provide an inline fallback to avoid truncation regressions.

-        <CardDescription className="text-[8px] text-gray-600 mb-2 line-clamp-2">
+        <CardDescription
+          className="text-[8px] text-gray-600 mb-2"
+          style={{
+            display: '-webkit-box',
+            WebkitLineClamp: 2,
+            WebkitBoxOrient: 'vertical',
+            overflow: 'hidden',
+          }}
+        >

Run to verify plugin config:

#!/bin/bash
fd -a 'tailwind.config*' --hidden --exclude node_modules
rg -nP --hidden --no-ignore-vcs --glob '!**/node_modules/**' "@tailwindcss/line-clamp|lineClamp" || true

25-91: Don’t ship mocks by default; export for Storybook/dev and remove default prop.

Defaulting to mockPostsData masks data‑flow issues and leaks demo content into prod. Export the mock for stories/dev only and require callers to pass real data.

Apply:

-// Mock data array
-const mockPostsData: PostData[] = [
+// Mock data array (exported for Storybook/dev usage only)
+export const mockPostsData: PostData[] = [

And:

-export default function PostCard({
-  posts = mockPostsData,
-  showMultiple = true,
-}: PostCardProps) {
+export default function PostCard({ posts, showMultiple = true }: PostCardProps) {

Also applies to: 221-224

🧹 Nitpick comments (6)
src/components/PostComponents/PostCard.tsx (6)

225-237: Guard for empty/undefined posts; avoid posts[0] crash and map on undefined.

Without the mock default (previous comment), posts can be empty/undefined. Add a safe fallback.

-  if (showMultiple) {
-    return (
-      <div className="flex flex-row justify-center gap-6 p-2">
-        {posts.map((post, index) => (
-          <SinglePostCard key={index} post={post} />
-        ))}
-      </div>
-    );
-  }
-
-  // Show only the first card if showMultiple is false
-  return <SinglePostCard post={posts[0]} />;
+  const data = posts ?? [];
+
+  if (showMultiple) {
+    return (
+      <div className="flex flex-row justify-center gap-6 p-2">
+        {data.map((post, index) => (
+          <SinglePostCard key={`${post.title}-${index}`} post={post} />
+        ))}
+      </div>
+    );
+  }
+
+  if (data.length === 0) return null; // or a Skeleton placeholder
+  return <SinglePostCard post={data[0]} />;

108-108: Fix nonstandard Tailwind sizes (w-54, h-66).

These classes don’t exist; use bracketed arbitrary values or scale tokens.

-    <Card className="w-54 h-66 font-sans">
+    <Card className="w-[216px] h-[264px] font-sans">

123-125: Use a down icon for Bearish to avoid mixed signals.

Swap icon based on sentiment for clearer UX.

-import { IoIosTrendingUp } from "react-icons/io";
+import { IoIosTrendingUp, IoIosTrendingDown } from "react-icons/io";
...
-            <IoIosTrendingUp className="w-2 h-2" />
-            {post.sentiment}
+            {post.sentiment === "Bearish" ? (
+              <IoIosTrendingDown className="w-2 h-2" />
+            ) : (
+              <IoIosTrendingUp className="w-2 h-2" />
+            )}
+            {post.sentiment}

132-148: Add a11y text for the sentiment bar.

Expose the distribution to screen readers.

-        <div className="mb-2">
+        <div
+          className="mb-2"
+          role="img"
+          aria-label={`Sentiment: ${post.sentimentBar.bullish}% bullish, ${post.sentimentBar.neutral}% neutral, ${post.sentimentBar.bearish}% bearish`}
+        >
           <div className="flex h-1 w-20 rounded overflow-hidden">
             <div
               className="bg-green-500"
               style={{ flex: post.sentimentBar.bullish }}
             ></div>
             <div
               className="bg-gray-300"
               style={{ flex: post.sentimentBar.neutral }}
             ></div>
             <div
               className="bg-red-500"
               style={{ flex: post.sentimentBar.bearish }}
             ></div>
           </div>
+          <span className="sr-only">
+            {`${post.sentimentBar.bullish}% bullish, ${post.sentimentBar.neutral}% neutral, ${post.sentimentBar.bearish}% bearish`}
+          </span>
         </div>

152-167: Flatten the source styling logic with a map to reduce branching.

Cleaner and easier to extend; avoids repeated class fragments.

-          {post.sources.map((src, idx) => {
-            let pillClass =
-              "px-2 py-0.5 rounded-full flex items-center gap-1 border text-[9px] font-medium";
-            let icon = null;
-            if (src.name === "Twitter") {
-              pillClass += " bg-blue-50 text-blue-600 border-blue-200";
-              icon = <FaTwitter className="w-2 h-2" />;
-            } else if (src.name === "LinkedIn") {
-              pillClass += " bg-blue-50 text-blue-600 border-blue-200";
-              icon = <FaLinkedin className="w-2 h-2" />;
-            } else if (src.name === "Reddit") {
-              pillClass += " bg-orange-50 text-orange-600 border-orange-200";
-              icon = <FaReddit className="w-2 h-2" />;
-            } else {
-              pillClass += " bg-gray-50 text-gray-600 border-gray-200";
-            }
-            return (
-              <span key={src.name + idx} className={pillClass}>
-                {icon}
-                {src.name} <span className="font-semibold">({src.count})</span>
-              </span>
-            );
-          })}
+          {post.sources.map((src, idx) => {
+            const base = "px-2 py-0.5 rounded-full flex items-center gap-1 border text-[9px] font-medium";
+            const map: Record<string, { cls: string; icon: JSX.Element | null }> = {
+              Twitter: { cls: " bg-blue-50 text-blue-600 border-blue-200", icon: <FaTwitter className="w-2 h-2" /> },
+              LinkedIn:{ cls: " bg-blue-50 text-blue-600 border-blue-200", icon: <FaLinkedin className="w-2 h-2" /> },
+              Reddit:  { cls: " bg-orange-50 text-orange-600 border-orange-200", icon: <FaReddit className="w-2 h-2" /> },
+            };
+            const { cls, icon } = map[src.name] ?? { cls: " bg-gray-50 text-gray-600 border-gray-200", icon: null };
+            return (
+              <span key={`${src.name}-${idx}`} className={base + cls}>
+                {icon}
+                {src.name} <span className="font-semibold">({src.count})</span>
+              </span>
+            );
+          })}

227-231: Prefer stable keys over array index.

Indexes hinder reconciliation on re‑orders. Use a stable composite key if no id exists.

If titles are unique in your dataset:

-        {data.map((post, index) => (
-          <SinglePostCard key={`${post.title}-${index}`} post={post} />
+        {data.map((post) => (
+          <SinglePostCard key={post.title} post={post} />
         ))}

Otherwise, keep a composite including a stable source (id/slug) and verify uniqueness in the feed.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f015145 and a923786.

📒 Files selected for processing (1)
  • src/components/PostComponents/PostCard.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/PostComponents/PostCard.tsx (1)
src/components/ui/card.tsx (4)
  • Card (91-91)
  • CardHeader (92-92)
  • CardTitle (94-94)
  • CardDescription (96-96)

@tasin2610 tasin2610 self-requested a review September 15, 2025 17:17
Copy link
Contributor

@tasin2610 tasin2610 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work on fixing all the reviews

@tasin2610 tasin2610 merged commit 18d6054 into main Sep 15, 2025
1 check was pending
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/PostComponents/PostsList.tsx (1)

8-8: Fetched posts are ignored and infinite scroll cannot trigger; wire data into PostGallery and add sentinel.

Right now PostGallery renders mock data and no sentinel exists, so pagination never fires. This mirrors a previously raised issue. Pass mapped posts and render the loadMoreRef element.

-import PostGallery from "./PostGallery";
+import PostGallery, { type PostData } from "./PostGallery";
   return (
-    <div className="mt-8">
-      <PostFilters
-        search={search}
-        onSearchChange={onChangeHandler}
-      />
-      <PostGallery/>
-    </div>
+    <div className="mt-8">
+      <PostFilters search={search} onSearchChange={onChangeHandler} />
+      <PostGallery posts={mappedPosts} />
+      <div ref={loadMoreRef} aria-hidden="true" className="h-px" />
+    </div>
   );

Add the adapter and mapping before the return:

// keep near other hooks or above return
const mappedPosts: PostData[] = mapPostsToPostData(info);

function mapPostsToPostData(posts: Post[]): PostData[] {
  return posts.map((p: any) => ({
    title: p.title ?? "Untitled",
    description: p.summary ?? p.content ?? "",
    sentiment: (p.sentiment ?? "NEUTRAL") as PostData["sentiment"],
    sentimentBar: p.sentimentBar ?? { bullish: 0, neutral: 100, bearish: 0 },
    postCount: p.postCount ?? 0,
    sources: (p.sources ?? []).map((s: any) => ({ name: s.name, count: s.count })),
    categories: p.categories ?? [],
    subcategories: p.subcategories ?? [],
  }));
}

Also, guard the observer against duplicate page loads:

// inside IntersectionObserver callback (around Line 88)
if (entries[0].isIntersecting && hasMore && !loading && !loadingMore && nextCursor) {
  fetchPosts(nextCursor);
}

Also applies to: 99-105

🧹 Nitpick comments (6)
src/components/PostComponents/TagRenderer.tsx (2)

25-27: Avoid magic number and prevent negative "+N more".

Compute remaining count robustly and name the constant.

-  const visibleTags = allTags.slice(0, 5);
-  const remainingCount = allTags.length - 5;
+  const MAX_VISIBLE = 5;
+  const visibleTags = allTags.slice(0, MAX_VISIBLE);
+  const remainingCount = Math.max(0, allTags.length - visibleTags.length);

33-37: 7px text is not readable; bump for a11y.

At this size the chips will fail basic readability. Suggest 10–12px.

-          className={`px-1 py-0.5 text-[7px] font-medium rounded-full ${
+          className={`px-1 py-0.5 text-[10px] font-medium rounded-full ${
...
-        <span className="px-1 py-0.5 text-[7px] font-medium text-gray-700 bg-gray-100 rounded-full">
+        <span className="px-1 py-0.5 text-[10px] font-medium text-gray-700 bg-gray-100 rounded-full">

Also applies to: 43-45

src/components/PostComponents/PostsList.tsx (1)

8-8: Type‑only imports; drop unused runtime imports.

SortField/SortOrder should be type-only to avoid bundling UI you removed; also remove unused SentimentBar/Spinner imports.

// replace
import { SortField, SortOrder } from "./PostSortSelect";
// with
import type { SortField, SortOrder } from "./PostSortSelect";

// and remove unused:
// import SentimentBar from "./SentimentBar";
// import Spinner from "./Spinner";
src/components/PostComponents/PostGallery.tsx (3)

16-25: Export PostData for reuse in PostsList mapping.

This enables typed adapters and prevents any.

-type PostData = {
+export type PostData = {
   title: string;
   description: string;
   sentiment: Sentiment;
   sentimentBar: { bullish: number; neutral: number; bearish: number };
   postCount: number;
   sources: Source[];
   categories: string[];
   subcategories?: string[];
 };

109-109: Tailwind sizes w-54/h-66 aren’t standard; likely no‑ops.

Use standard scale or configured tokens.

-    <Card className="w-54 h-66 font-sans">
+    <Card className="w-56 h-64 font-sans">

195-197: Prefer stable keys over array index.

If the order changes, index keys can cause visual glitches. Consider adding an id to PostData and using it here.

Example:

export type PostData = { id: string; /* ... */ };
// ...
<SinglePostCard key={post.id} post={post} />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a923786 and 653ac07.

📒 Files selected for processing (3)
  • src/components/PostComponents/PostGallery.tsx (1 hunks)
  • src/components/PostComponents/PostsList.tsx (2 hunks)
  • src/components/PostComponents/TagRenderer.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/PostComponents/PostsList.tsx (1)
src/components/PostComponents/PostFilters.tsx (1)
  • PostFilters (10-26)


import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
import TagRenderer from "./TagRenderer";
import { Sentiment } from "@prisma/client";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Do not import Prisma on the client; use a type‑only import.

Importing from @prisma/client in a client component can bloat/break the bundle. Use import type.

-import { Sentiment } from "@prisma/client";
+import type { Sentiment } from "@prisma/client";

To verify no runtime Prisma leaks in client code:


🏁 Script executed:

#!/bin/bash
# Find client components importing @prisma/client as a value (not type-only)
rg -nP --type tsx -C2 '@prisma/client' src/components

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Searching for @prisma/client imports in src..."
rg -n --hidden -S "@prisma/client" src || true

echo
echo "Showing matches with 2 lines of context:"
rg -n --hidden -S -C2 "@prisma/client" src || true

echo
if [ -f src/components/PostComponents/PostGallery.tsx ]; then
  echo "Contents of src/components/PostComponents/PostGallery.tsx (first 200 lines):"
  sed -n '1,200p' src/components/PostComponents/PostGallery.tsx || true
else
  echo "File not found: src/components/PostComponents/PostGallery.tsx"
fi

Length of output: 8492


Replace value import with a type-only import for Sentiment in this client component

This is a client component (has "use client" at the top); importing from @prisma/client as a value will pull Prisma runtime into the client bundle — use a type-only import.

Location: src/components/PostComponents/PostGallery.tsx — line 9

-import { Sentiment } from "@prisma/client";
+import type { Sentiment } from "@prisma/client";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Sentiment } from "@prisma/client";
import type { Sentiment } from "@prisma/client";
🤖 Prompt for AI Agents
In src/components/PostComponents/PostGallery.tsx around line 9, the file
currently imports Sentiment from @prisma/client as a value which will pull
Prisma runtime into the client bundle; change that import to a type-only import
so Sentiment is erased at runtime (use the TypeScript "import type" form),
ensuring the component remains a client component without including Prisma
runtime.

Comment on lines +191 to +198
posts = mockPostsData,
}: PostsGalleryProps) {
return (
<div className="flex flex-row justify-center gap-6 p-2">
{posts.map((post, index) => (
<SinglePostCard key={index} post={post} />
))}
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid shipping mock data by default.

Defaulting posts to mockPostsData risks mock UI in production. Make posts explicit and fallback inside only when truly needed.

-export default function PostsGallery({
-  posts = mockPostsData,
-}: PostsGalleryProps) {
-  return (
-    <div className="flex flex-row justify-center gap-6 p-2">
-      {posts.map((post, index) => (
+export default function PostsGallery({ posts }: PostsGalleryProps) {
+  const data = posts ?? mockPostsData;
+  return (
+    <div className="flex flex-row justify-center gap-6 p-2">
+      {data.map((post, index) => (
         <SinglePostCard key={index} post={post} />
       ))}
     </div>
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
posts = mockPostsData,
}: PostsGalleryProps) {
return (
<div className="flex flex-row justify-center gap-6 p-2">
{posts.map((post, index) => (
<SinglePostCard key={index} post={post} />
))}
</div>
export default function PostsGallery({ posts }: PostsGalleryProps) {
const data = posts ?? mockPostsData;
return (
<div className="flex flex-row justify-center gap-6 p-2">
{data.map((post, index) => (
<SinglePostCard key={index} post={post} />
))}
</div>
);
}
🤖 Prompt for AI Agents
In src/components/PostComponents/PostGallery.tsx around lines 191 to 198, the
component currently defaults the posts prop to mockPostsData which can leak mock
UI to production; remove the default assignment from the function signature so
posts must be passed in (or make the prop required in the PostsGalleryProps
type), and if a safe fallback is needed inside the component use a non-mock
fallback like posts = postsProp || [] or explicitly use mockPostsData only in
story/test code — update types and callers accordingly to ensure production code
never ships with mock data by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants