From f015145f6cdd863b6fad4e61d32d530cd45faea3 Mon Sep 17 00:00:00 2001 From: Simon Smilga Date: Mon, 15 Sep 2025 10:00:08 -0600 Subject: [PATCH 1/3] feat: Refactor PostCard and PostFilters components for improved UI and functionality --- src/components/PostComponents/PostCard.tsx | 317 ++++++++++++------ src/components/PostComponents/PostFilters.tsx | 74 +--- src/components/PostComponents/PostsList.tsx | 71 +--- 3 files changed, 223 insertions(+), 239 deletions(-) diff --git a/src/components/PostComponents/PostCard.tsx b/src/components/PostComponents/PostCard.tsx index f114468..fb7e5b0 100644 --- a/src/components/PostComponents/PostCard.tsx +++ b/src/components/PostComponents/PostCard.tsx @@ -1,117 +1,228 @@ "use client"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { getRelativeTime } from "@/lib/date"; -import React from "react"; -import { - FaGlobe, - FaHashtag, - FaReddit, - FaTelegram, - FaTwitter, -} from "react-icons/fa"; -import { - MdSentimentDissatisfied, - MdSentimentNeutral, - MdSentimentSatisfied, -} from "react-icons/md"; -import type { Post } from "@prisma/client"; +import { IoIosTrendingUp } from "react-icons/io"; +import { CiChat1 } from "react-icons/ci"; +import { FaTwitter, FaLinkedin, FaReddit } from "react-icons/fa"; -type PostCardProps = { - post: Post; +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; + +type Source = { + name: string; + icon: React.ReactNode; + count: number; + color: string; }; -const sentimentIcon = (sentiment: string) => { - if (sentiment === "BULLISH") - return ; - if (sentiment === "NEUTRAL") - return ; - if (sentiment === "BEARISH") - return ; - return ; +type PostData = { + title: string; + description: string; + sentiment: "Bullish" | "Bearish" | "Neutral"; + sentimentBar: { bullish: number; neutral: number; bearish: number }; + postCount: number; + sources: Source[]; + categories: string[]; + extraTags?: string; }; -const platformIcon = (source: string) => { - if (source === "TWITTER") return ; - if (source === "REDDIT") return ; - if (source === "TELEGRAM") return ; - return ; +// Mock data array +const mockPostsData: PostData[] = [ + { + title: "Bitcoin Institutional Adoption & Market Sentiment", + description: + "Major financial institutions showing increased interest in Bitcoin with positive analyst reports...", + sentiment: "Bullish", + sentimentBar: { bullish: 60, neutral: 25, bearish: 15 }, + postCount: 24, + sources: [ + { + name: "Twitter", + icon: , + count: 12, + color: "text-blue-500", + }, + { + name: "LinkedIn", + icon: , + count: 8, + color: "text-blue-600", + }, + { + name: "Reddit", + icon: , + count: 4, + color: "text-orange-600", + }, + ], + categories: ["Bitcoin", "Institutional", "adoption"], + extraTags: "+1", + }, + { + title: "Ethereum DeFi Protocol Updates", + description: + "New developments in decentralized finance protocols showing promising growth patterns...", + sentiment: "Neutral", + sentimentBar: { bullish: 30, neutral: 50, bearish: 20 }, + postCount: 18, + sources: [ + { + name: "Twitter", + icon: , + count: 10, + color: "text-blue-500", + }, + { + name: "Reddit", + icon: , + count: 8, + color: "text-orange-600", + }, + ], + categories: ["Ethereum", "DeFi", "Protocol"], + extraTags: "+2", + }, + { + title: "Market Volatility Analysis", + description: + "Recent market downturn showing concerning trends across major cryptocurrencies...", + sentiment: "Bearish", + sentimentBar: { bullish: 15, neutral: 25, bearish: 60 }, + postCount: 32, + sources: [ + { + name: "LinkedIn", + icon: , + count: 15, + color: "text-blue-600", + }, + { + name: "Reddit", + icon: , + count: 17, + color: "text-orange-600", + }, + ], + categories: ["Market", "Volatility", "Analysis"], + extraTags: "+3", + }, +]; + +type PostCardProps = { + posts?: PostData[]; + showMultiple?: boolean; }; -const PostCard: React.FC = ({ post }) => ( - - -
- {(post.categories?.length || post.subcategories?.length) && ( -
- {(post.categories ?? []).map((cat: string, idx: number) => ( - - - {cat} - - ))} - {(post.subcategories ?? []).map((subcat: string, idx: number) => ( - - - {subcat} - - ))} -
- )} - +// Single card component +export function SinglePostCard({ post }: { post: PostData }) { + const sentimentColor = + post.sentiment === "Bullish" + ? "text-green-700 bg-green-100" + : post.sentiment === "Bearish" + ? "text-red-700 bg-red-100" + : "text-gray-700 bg-gray-100"; + + return ( + + + {post.title} -
-
- -
- - {post.content && post.content.length > 56 - ? post.content.slice(0, 56) + "..." - : post.content} - -
-
-
-
-
-
+ + + {post.description} + + + {/* Sentiment indicators */} +
+ + + {post.sentiment} + +
+ + {post.postCount} +
+
+ + {/* Sentiment Bar */} +
+
+
+
+
+
- - {sentimentIcon(post.sentiment)} - {post.sentiment} - - - {platformIcon(post.source)} - {post.source} - - - {/* Time removed as signalTime is not in schema */} - {post.createdAt - ? getRelativeTime( - typeof post.createdAt === "string" - ? post.createdAt - : post.createdAt.toISOString() - ) - : "Invalid date"} - + + {/* Source Tags */} +
+ {post.sources.map((src, idx) => { + let pillClass = + "px-2 py-0.5 rounded-full flex items-center gap-1 border text-[9px] font-medium"; + if (src.name === "Twitter") + pillClass += " bg-blue-50 text-blue-600 border-blue-200"; + else if (src.name === "LinkedIn") + pillClass += " bg-blue-50 text-blue-600 border-blue-200"; + else if (src.name === "Reddit") + pillClass += " bg-orange-50 text-orange-600 border-orange-200"; + else pillClass += " bg-gray-50 text-gray-600 border-gray-200"; + return ( + + {src.icon} + {src.name} ({src.count}) + + ); + })} +
+ + {/* Category/Subcategory Tags */} +
+ {post.categories.map((cat, idx) => ( + + #{cat} + + ))} + {post.extraTags && ( + + {post.extraTags} + + )} +
+ + + ); +} + +export default function PostCard({ + posts = mockPostsData, + showMultiple = true, +}: PostCardProps) { + if (showMultiple) { + return ( +
+ {posts.map((post, index) => ( + + ))}
- - -); + ); + } -export default PostCard; + // Show only the first card if showMultiple is false + return ; +} diff --git a/src/components/PostComponents/PostFilters.tsx b/src/components/PostComponents/PostFilters.tsx index 1894135..f260eee 100644 --- a/src/components/PostComponents/PostFilters.tsx +++ b/src/components/PostComponents/PostFilters.tsx @@ -1,38 +1,18 @@ -import { - Select, - SelectItem, -} from "@/components/ui/select"; +import { Select, SelectItem } from "@/components/ui/select"; import { Input } from "@/components/ui/input"; import { PostSortSelect, SortField, SortOrder } from "./PostSortSelect"; interface PostFiltersProps { search: string; onSearchChange: (e: React.ChangeEvent) => void; - sortField: SortField; - sortOrder: SortOrder; - onSortFieldChange: (field: SortField) => void; - onSortOrderChange: (order: SortOrder) => void; - sentimentFilter: string; - onSentimentChange: (value: string) => void; - sourceFilter: string; - onSourceChange: (value: string) => void; } export default function PostFilters({ search, onSearchChange, - sortField, - sortOrder, - onSortFieldChange, - onSortOrderChange, - sentimentFilter, - onSentimentChange, - sourceFilter, - onSourceChange, }: PostFiltersProps) { return ( -
- {/* Search Bar */} +
- - {/* Filters & Sorters Card */} -
- {/* Sorters */} -
- -
- -
-
- - {/* Filters */} -
-
- - -
- -
- - -
-
-
); } diff --git a/src/components/PostComponents/PostsList.tsx b/src/components/PostComponents/PostsList.tsx index d776ed6..159f2ab 100644 --- a/src/components/PostComponents/PostsList.tsx +++ b/src/components/PostComponents/PostsList.tsx @@ -96,69 +96,12 @@ export default function PostsList() { }, [hasMore, loading, nextCursor, fetchPosts]); return ( - <> -
- -
- - {!loading && !error && info.length > 0 && ( - - (sentimentFilter === "all" || - post.sentiment === sentimentFilter) && - (sourceFilter === "all" || post.source === sourceFilter) - )} - /> - )} - - {error &&
{error}
} - - {loading && ( -
- -
- )} - - {!loading && !error && !info.length && ( -
No Posts Found
- )} - - {!loading && !error && info.length > 0 && ( -
- {info - .filter( - (post) => - (sentimentFilter === "all" || - post.sentiment === sentimentFilter) && - (sourceFilter === "all" || post.source === sourceFilter) - ) - .map((post: Post) => ( - - ))} - {/* Infinite scroll trigger */} - {hasMore && ( -
- {loadingMore ? ( - - ) : ( - Loading more... - )} -
- )} -
- )} - +
+ + +
); } From a923786e7fa0b8ee51bdc7bd94f55cd48ee9aa5c Mon Sep 17 00:00:00 2001 From: Simon Smilga Date: Mon, 15 Sep 2025 10:55:48 -0600 Subject: [PATCH 2/3] feat: Refactor PostCard component to streamline source handling and enhance category/subcategory display --- src/components/PostComponents/PostCard.tsx | 101 +++++++++++---------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/src/components/PostComponents/PostCard.tsx b/src/components/PostComponents/PostCard.tsx index fb7e5b0..fdc7542 100644 --- a/src/components/PostComponents/PostCard.tsx +++ b/src/components/PostComponents/PostCard.tsx @@ -8,9 +8,7 @@ import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; type Source = { name: string; - icon: React.ReactNode; count: number; - color: string; }; type PostData = { @@ -21,7 +19,7 @@ type PostData = { postCount: number; sources: Source[]; categories: string[]; - extraTags?: string; + subcategories?: string[]; }; // Mock data array @@ -36,25 +34,19 @@ const mockPostsData: PostData[] = [ sources: [ { name: "Twitter", - icon: , count: 12, - color: "text-blue-500", }, { name: "LinkedIn", - icon: , count: 8, - color: "text-blue-600", }, { name: "Reddit", - icon: , count: 4, - color: "text-orange-600", }, ], - categories: ["Bitcoin", "Institutional", "adoption"], - extraTags: "+1", + categories: ["Bitcoin", "Institutional"], + subcategories: ["ETF", "Regulation", "Spot", "Futures"], }, { title: "Ethereum DeFi Protocol Updates", @@ -66,19 +58,15 @@ const mockPostsData: PostData[] = [ sources: [ { name: "Twitter", - icon: , count: 10, - color: "text-blue-500", }, { name: "Reddit", - icon: , count: 8, - color: "text-orange-600", }, ], - categories: ["Ethereum", "DeFi", "Protocol"], - extraTags: "+2", + categories: ["Ethereum"], + subcategories: ["Lending", "DEX", "Staking", "Yield", "Bridge"], }, { title: "Market Volatility Analysis", @@ -90,19 +78,15 @@ const mockPostsData: PostData[] = [ sources: [ { name: "LinkedIn", - icon: , count: 15, - color: "text-blue-600", }, { name: "Reddit", - icon: , count: 17, - color: "text-orange-600", }, ], - categories: ["Market", "Volatility", "Analysis"], - extraTags: "+3", + categories: ["Market", "Volatility"], + subcategories: ["Crash", "Recovery", "Bear", "Bull"], }, ]; @@ -121,7 +105,7 @@ export function SinglePostCard({ post }: { post: PostData }) { : "text-gray-700 bg-gray-100"; return ( - + {post.title} @@ -168,16 +152,22 @@ export function SinglePostCard({ post }: { post: PostData }) { {post.sources.map((src, idx) => { let pillClass = "px-2 py-0.5 rounded-full flex items-center gap-1 border text-[9px] font-medium"; - if (src.name === "Twitter") + let icon = null; + if (src.name === "Twitter") { pillClass += " bg-blue-50 text-blue-600 border-blue-200"; - else if (src.name === "LinkedIn") + icon = ; + } else if (src.name === "LinkedIn") { pillClass += " bg-blue-50 text-blue-600 border-blue-200"; - else if (src.name === "Reddit") + icon = ; + } else if (src.name === "Reddit") { pillClass += " bg-orange-50 text-orange-600 border-orange-200"; - else pillClass += " bg-gray-50 text-gray-600 border-gray-200"; + icon = ; + } else { + pillClass += " bg-gray-50 text-gray-600 border-gray-200"; + } return ( - {src.icon} + {icon} {src.name} ({src.count}) ); @@ -186,23 +176,42 @@ export function SinglePostCard({ post }: { post: PostData }) { {/* Category/Subcategory Tags */}
- {post.categories.map((cat, idx) => ( - - #{cat} - - ))} - {post.extraTags && ( - - {post.extraTags} - - )} + {(() => { + const allTags = [ + ...post.categories.map((cat) => ({ + type: "category", + value: cat, + })), + ...(post.subcategories || []).map((subcat) => ({ + type: "subcategory", + value: subcat, + })), + ]; + const visibleTags = allTags.slice(0, 5); + const remainingCount = allTags.length - 5; + + return ( + <> + {visibleTags.map((tag, idx) => ( + + #{tag.value} + + ))} + {remainingCount > 0 && ( + + +{remainingCount} more + + )} + + ); + })()}
From 653ac075320fbad7eba8285271eb92150a61564f Mon Sep 17 00:00:00 2001 From: Simon Smilga Date: Mon, 15 Sep 2025 11:15:01 -0600 Subject: [PATCH 3/3] feat: Add PostGallery and TagRenderer components for enhanced post display and categorization --- .../{PostCard.tsx => PostGallery.tsx} | 81 +++++-------------- src/components/PostComponents/PostsList.tsx | 4 +- src/components/PostComponents/TagRenderer.tsx | 49 +++++++++++ 3 files changed, 73 insertions(+), 61 deletions(-) rename src/components/PostComponents/{PostCard.tsx => PostGallery.tsx} (72%) create mode 100644 src/components/PostComponents/TagRenderer.tsx diff --git a/src/components/PostComponents/PostCard.tsx b/src/components/PostComponents/PostGallery.tsx similarity index 72% rename from src/components/PostComponents/PostCard.tsx rename to src/components/PostComponents/PostGallery.tsx index fdc7542..13fc099 100644 --- a/src/components/PostComponents/PostCard.tsx +++ b/src/components/PostComponents/PostGallery.tsx @@ -5,6 +5,8 @@ import { CiChat1 } from "react-icons/ci"; import { FaTwitter, FaLinkedin, FaReddit } from "react-icons/fa"; import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; +import TagRenderer from "./TagRenderer"; +import { Sentiment } from "@prisma/client"; type Source = { name: string; @@ -14,7 +16,7 @@ type Source = { type PostData = { title: string; description: string; - sentiment: "Bullish" | "Bearish" | "Neutral"; + sentiment: Sentiment; sentimentBar: { bullish: number; neutral: number; bearish: number }; postCount: number; sources: Source[]; @@ -28,7 +30,7 @@ const mockPostsData: PostData[] = [ title: "Bitcoin Institutional Adoption & Market Sentiment", description: "Major financial institutions showing increased interest in Bitcoin with positive analyst reports...", - sentiment: "Bullish", + sentiment: "BULLISH", sentimentBar: { bullish: 60, neutral: 25, bearish: 15 }, postCount: 24, sources: [ @@ -52,7 +54,7 @@ const mockPostsData: PostData[] = [ title: "Ethereum DeFi Protocol Updates", description: "New developments in decentralized finance protocols showing promising growth patterns...", - sentiment: "Neutral", + sentiment: "NEUTRAL", sentimentBar: { bullish: 30, neutral: 50, bearish: 20 }, postCount: 18, sources: [ @@ -72,7 +74,7 @@ const mockPostsData: PostData[] = [ title: "Market Volatility Analysis", description: "Recent market downturn showing concerning trends across major cryptocurrencies...", - sentiment: "Bearish", + sentiment: "BEARISH", sentimentBar: { bullish: 15, neutral: 25, bearish: 60 }, postCount: 32, sources: [ @@ -90,17 +92,16 @@ const mockPostsData: PostData[] = [ }, ]; -type PostCardProps = { +type PostsGalleryProps = { posts?: PostData[]; - showMultiple?: boolean; }; // Single card component export function SinglePostCard({ post }: { post: PostData }) { const sentimentColor = - post.sentiment === "Bullish" + post.sentiment === "BULLISH" ? "text-green-700 bg-green-100" - : post.sentiment === "Bearish" + : post.sentiment === "BEARISH" ? "text-red-700 bg-red-100" : "text-gray-700 bg-gray-100"; @@ -176,62 +177,24 @@ export function SinglePostCard({ post }: { post: PostData }) { {/* Category/Subcategory Tags */}
- {(() => { - const allTags = [ - ...post.categories.map((cat) => ({ - type: "category", - value: cat, - })), - ...(post.subcategories || []).map((subcat) => ({ - type: "subcategory", - value: subcat, - })), - ]; - const visibleTags = allTags.slice(0, 5); - const remainingCount = allTags.length - 5; - - return ( - <> - {visibleTags.map((tag, idx) => ( - - #{tag.value} - - ))} - {remainingCount > 0 && ( - - +{remainingCount} more - - )} - - ); - })()} +
); } -export default function PostCard({ +export default function PostsGallery({ posts = mockPostsData, - showMultiple = true, -}: PostCardProps) { - if (showMultiple) { - return ( -
- {posts.map((post, index) => ( - - ))} -
- ); - } - - // Show only the first card if showMultiple is false - return ; +}: PostsGalleryProps) { + return ( +
+ {posts.map((post, index) => ( + + ))} +
+ ); } diff --git a/src/components/PostComponents/PostsList.tsx b/src/components/PostComponents/PostsList.tsx index 159f2ab..ec1e560 100644 --- a/src/components/PostComponents/PostsList.tsx +++ b/src/components/PostComponents/PostsList.tsx @@ -1,11 +1,11 @@ "use client"; import { useEffect, useState, useRef, useCallback } from "react"; -import PostCard from "./PostCard"; import SentimentBar from "./SentimentBar"; import PostFilters from "./PostFilters"; import { SortField, SortOrder } from "./PostSortSelect"; import Spinner from "./Spinner"; import type { Post } from "@prisma/client"; +import PostGallery from "./PostGallery"; export default function PostsList() { const [error, setError] = useState(null); @@ -101,7 +101,7 @@ export default function PostsList() { search={search} onSearchChange={onChangeHandler} /> - +
); } diff --git a/src/components/PostComponents/TagRenderer.tsx b/src/components/PostComponents/TagRenderer.tsx new file mode 100644 index 0000000..03f76cf --- /dev/null +++ b/src/components/PostComponents/TagRenderer.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +type Tag = { + type: "category" | "subcategory"; + value: string; +}; + +type TagRendererProps = { + categories: string[]; + subcategories?: string[]; +}; + +export default function TagRenderer({ + categories, + subcategories, +}: TagRendererProps) { + const allTags: Tag[] = [ + ...categories.map((cat) => ({ type: "category" as const, value: cat })), + ...(subcategories || []).map((subcat) => ({ + type: "subcategory" as const, + value: subcat, + })), + ]; + + const visibleTags = allTags.slice(0, 5); + const remainingCount = allTags.length - 5; + + return ( + <> + {visibleTags.map((tag, idx) => ( + + #{tag.value} + + ))} + {remainingCount > 0 && ( + + +{remainingCount} more + + )} + + ); +}