diff --git a/src/components/PostComponents/PostCard.tsx b/src/components/PostComponents/PostCard.tsx deleted file mode 100644 index f114468..0000000 --- a/src/components/PostComponents/PostCard.tsx +++ /dev/null @@ -1,117 +0,0 @@ -"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"; - -type PostCardProps = { - post: Post; -}; - -const sentimentIcon = (sentiment: string) => { - if (sentiment === "BULLISH") - return ; - if (sentiment === "NEUTRAL") - return ; - if (sentiment === "BEARISH") - return ; - return ; -}; - -const platformIcon = (source: string) => { - if (source === "TWITTER") return ; - if (source === "REDDIT") return ; - if (source === "TELEGRAM") return ; - return ; -}; - -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} - - ))} -
- )} - - {post.title} - -
-
- -
- - {post.content && post.content.length > 56 - ? post.content.slice(0, 56) + "..." - : post.content} - -
-
-
-
-
-
-
- - {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"} - -
- - -); - -export default PostCard; 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/PostGallery.tsx b/src/components/PostComponents/PostGallery.tsx new file mode 100644 index 0000000..13fc099 --- /dev/null +++ b/src/components/PostComponents/PostGallery.tsx @@ -0,0 +1,200 @@ +"use client"; + +import { IoIosTrendingUp } from "react-icons/io"; +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; + count: number; +}; + +type PostData = { + title: string; + description: string; + sentiment: Sentiment; + sentimentBar: { bullish: number; neutral: number; bearish: number }; + postCount: number; + sources: Source[]; + categories: string[]; + subcategories?: string[]; +}; + +// 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", + count: 12, + }, + { + name: "LinkedIn", + count: 8, + }, + { + name: "Reddit", + count: 4, + }, + ], + categories: ["Bitcoin", "Institutional"], + subcategories: ["ETF", "Regulation", "Spot", "Futures"], + }, + { + 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", + count: 10, + }, + { + name: "Reddit", + count: 8, + }, + ], + categories: ["Ethereum"], + subcategories: ["Lending", "DEX", "Staking", "Yield", "Bridge"], + }, + { + 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", + count: 15, + }, + { + name: "Reddit", + count: 17, + }, + ], + categories: ["Market", "Volatility"], + subcategories: ["Crash", "Recovery", "Bear", "Bull"], + }, +]; + +type PostsGalleryProps = { + posts?: PostData[]; +}; + +// 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.description} + + + {/* Sentiment indicators */} +
+ + + {post.sentiment} + +
+ + {post.postCount} +
+
+ + {/* Sentiment Bar */} +
+
+
+
+
+
+
+ + {/* 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"; + let icon = null; + if (src.name === "Twitter") { + pillClass += " bg-blue-50 text-blue-600 border-blue-200"; + icon = ; + } else if (src.name === "LinkedIn") { + pillClass += " bg-blue-50 text-blue-600 border-blue-200"; + icon = ; + } else if (src.name === "Reddit") { + pillClass += " bg-orange-50 text-orange-600 border-orange-200"; + icon = ; + } else { + pillClass += " bg-gray-50 text-gray-600 border-gray-200"; + } + return ( + + {icon} + {src.name} ({src.count}) + + ); + })} +
+ + {/* Category/Subcategory Tags */} +
+ +
+
+
+ ); +} + +export default function PostsGallery({ + posts = mockPostsData, +}: PostsGalleryProps) { + return ( +
+ {posts.map((post, index) => ( + + ))} +
+ ); +} diff --git a/src/components/PostComponents/PostsList.tsx b/src/components/PostComponents/PostsList.tsx index d776ed6..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); @@ -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... - )} -
- )} -
- )} - +
+ + +
); } 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 + + )} + + ); +}