diff --git a/app/(landing)/projects/[id]/page.tsx b/app/(landing)/projects/[id]/page.tsx index 02d68139d..140bc2e46 100644 --- a/app/(landing)/projects/[id]/page.tsx +++ b/app/(landing)/projects/[id]/page.tsx @@ -68,6 +68,7 @@ async function ProjectContent({ id }: { id: string }) { // Try to fetch as crowdfunding project first try { const response = await getCrowdfundingProject(id); + if (response.success && response.data) { projectData = transformCrowdfundingProject( response.data.project, @@ -80,6 +81,7 @@ async function ProjectContent({ id }: { id: string }) { // If crowdfunding project fails, try regular project try { const regularProject = await getProjectDetails(id); + // Transform regular project data if needed // For regular projects, we need to create a mock CrowdfundingProject structure const mockProject: CrowdfundingProject = { diff --git a/components/project-details/comment-section/comment-input.tsx b/components/project-details/comment-section/comment-input.tsx new file mode 100644 index 000000000..5f052c147 --- /dev/null +++ b/components/project-details/comment-section/comment-input.tsx @@ -0,0 +1,74 @@ +'use client'; +import type React from 'react'; +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Smile } from 'lucide-react'; + +interface CommentInputProps { + onSubmit: (content: string) => void; + placeholder?: string; + autoFocus?: boolean; + onCancel?: () => void; + showCancel?: boolean; +} + +export function CommentInput({ + onSubmit, + placeholder = 'Leave a comment...', + autoFocus = false, + onCancel, + showCancel = false, +}: CommentInputProps) { + const [comment, setComment] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (comment.trim()) { + onSubmit(comment.trim()); + setComment(''); + } + }; + + return ( +
+
+ +
+ +
+ setComment(e.target.value)} + placeholder={placeholder} + autoFocus={autoFocus} + className='w-full text-base text-neutral-200 placeholder:text-neutral-500 focus:outline-none' + /> +
+ + + + {showCancel && ( + + )} +
+ ); +} diff --git a/components/project-details/comment-section/comment-item.tsx b/components/project-details/comment-section/comment-item.tsx new file mode 100644 index 000000000..a493f2f3e --- /dev/null +++ b/components/project-details/comment-section/comment-item.tsx @@ -0,0 +1,129 @@ +'use client'; + +import { useState } from 'react'; +import { Heart, ChevronUp, ChevronDown } from 'lucide-react'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; +import { CommentInput } from './comment-input'; +import { Comment } from '@/lib/data/comments-mock'; + +interface CommentItemProps { + comment: Comment; + isReply?: boolean; + onAddReply: (commentId: string, content: string) => void; +} + +export function CommentItem({ + comment, + isReply = false, + onAddReply, +}: CommentItemProps) { + const [isLiked, setIsLiked] = useState(false); + const [showReplies, setShowReplies] = useState(false); + const [showReplyInput, setShowReplyInput] = useState(false); + const hasReplies = comment.replies && comment.replies.length > 0; + + const handleReplySubmit = (content: string) => { + onAddReply(comment.id, content); + setShowReplyInput(false); + setShowReplies(true); + }; + + return ( +
+ + + {comment.author.name[0]} + + +
+
+
+ +

+ {comment.content} +

+
+
+ +
+ + {comment.timestamp} + + + +
+ + {showReplyInput && ( +
+ setShowReplyInput(false)} + showCancel + /> +
+ )} + + {hasReplies && ( + <> + + + {showReplies && ( +
+ {comment.replies?.map(reply => ( + + ))} +
+ )} + + )} +
+
+ ); +} diff --git a/components/project-details/comment-section/comments-empty-state.tsx b/components/project-details/comment-section/comments-empty-state.tsx new file mode 100644 index 000000000..f84405ccd --- /dev/null +++ b/components/project-details/comment-section/comments-empty-state.tsx @@ -0,0 +1,30 @@ +'use client'; + +import Image from 'next/image'; +import { CommentInput } from './comment-input'; + +interface CommentsEmptyStateProps { + onAddComment: (content: string) => void; +} + +export function CommentsEmptyState({ onAddComment }: CommentsEmptyStateProps) { + return ( +
+
+
+ No comments yet +
+

+ Be the first to Leave a Comment +

+
+ +
+ ); +} diff --git a/components/project-details/comment-section/comments-sort-dropdown.tsx b/components/project-details/comment-section/comments-sort-dropdown.tsx new file mode 100644 index 000000000..dc4df738b --- /dev/null +++ b/components/project-details/comment-section/comments-sort-dropdown.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { useState } from 'react'; +import { ListFilter } from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Button } from '@/components/ui/button'; + +type SortOption = 'latest' | 'oldest' | 'most-relevant'; + +const sortLabels: Record = { + latest: 'Latest', + oldest: 'Oldest', + 'most-relevant': 'Most relevant', +}; + +export function CommentsSortDropdown() { + const [sortBy, setSortBy] = useState('latest'); + + return ( + + + + + + setSortBy(value as SortOption)} + > + + Latest + + + Oldest + + + Most relevant + + + + + ); +} diff --git a/components/project-details/comment-section/project-comments.tsx b/components/project-details/comment-section/project-comments.tsx new file mode 100644 index 000000000..bbcfc7819 --- /dev/null +++ b/components/project-details/comment-section/project-comments.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useState } from 'react'; +import { CommentsSortDropdown } from './comments-sort-dropdown'; +import { CommentItem } from './comment-item'; +import { CommentInput } from './comment-input'; +import { CommentsEmptyState } from './comments-empty-state'; +import { mockComments, type Comment } from '@/lib/data/comments-mock'; + +export function ProjectComments() { + const [comments, setComments] = useState(mockComments); + const isEmpty = comments.length === 0; + + const handleAddComment = (content: string) => { + const newComment: Comment = { + id: Date.now().toString(), + author: { + name: 'User', + avatar: '/diverse-user-avatars.png', + }, + content, + timestamp: 'Just now', + likes: 0, + replies: [], + }; + setComments([newComment, ...comments]); + }; + + const handleAddReply = (commentId: string, content: string) => { + const newReply: Comment = { + id: `${commentId}-${Date.now()}`, + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content, + timestamp: 'Just now', + likes: 0, + }; + + const addReplyToComment = (comments: Comment[]): Comment[] => { + return comments.map(comment => { + if (comment.id === commentId) { + return { + ...comment, + replies: [...(comment.replies || []), newReply], + }; + } + if (comment.replies) { + return { + ...comment, + replies: addReplyToComment(comment.replies), + }; + } + return comment; + }); + }; + + setComments(addReplyToComment(comments)); + }; + + if (isEmpty) { + return ; + } + + return ( +
+
+ +
+ +
+ {comments.map(comment => ( + + ))} +
+ + +
+ ); +} diff --git a/components/project-details/project-layout.tsx b/components/project-details/project-layout.tsx index 49dfab7ad..0ed05782c 100644 --- a/components/project-details/project-layout.tsx +++ b/components/project-details/project-layout.tsx @@ -8,6 +8,7 @@ import { ProjectAbout } from './project-about'; import { useIsMobile } from '@/hooks/use-mobile'; import { CrowdfundingProject, CrowdfundData } from '@/lib/api/types'; import { ChevronLeftCircle, ChevronRightCircle } from 'lucide-react'; +import { ProjectComments } from './comment-section/project-comments'; interface ProjectLayoutProps { project: CrowdfundingProject & { @@ -278,13 +279,13 @@ export function ProjectLayout({ project }: ProjectLayoutProps) { -
Voters content coming soon...
-
-
- Comments content coming soon... + Voters content coming soon.rgngogogo5gmomo..
+ + + @@ -436,9 +437,7 @@ export function ProjectLayout({ project }: ProjectLayoutProps) { -
- Comments content coming soon... -
+
diff --git a/lib/data/comments-mock.ts b/lib/data/comments-mock.ts new file mode 100644 index 000000000..113653d5b --- /dev/null +++ b/lib/data/comments-mock.ts @@ -0,0 +1,104 @@ +export interface Comment { + id: string; + author: { + name: string; + avatar: string; + }; + content: string; + timestamp: string; + likes: number; + replies?: Comment[]; +} + +export const mockComments: Comment[] = [ + { + id: '1', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + replies: [ + { + id: '1-1', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, + { + id: '1-2', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, + { + id: '1-3', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, + { + id: '1-4', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, + { + id: '1-5', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, + ], + }, + { + id: '2', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, + { + id: '3', + author: { + name: 'User', + avatar: '/user-icon.png', + }, + content: + 'I think this project has a lot of potential 🚀 The idea of bringing blockchain into healthcare for transparency is brilliant 💡', + timestamp: '2h', + likes: 5, + }, +]; diff --git a/public/Humanoid.png b/public/Humanoid.png new file mode 100644 index 000000000..4782b5211 Binary files /dev/null and b/public/Humanoid.png differ diff --git a/public/user-icon.png b/public/user-icon.png new file mode 100644 index 000000000..5509a040c Binary files /dev/null and b/public/user-icon.png differ