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 (
+
+ );
+}
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 (
+
+
+
+
+
+
+ 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