Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions app/components/discussions/DiscussionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

"use client";
import { useState } from "react";
import { Discussion } from "./types";
import ReplySection from "./ReplySection";
import { ThumbsUp, MessageSquare, Clock, CheckCircle2, ChevronDown, ChevronUp } from "lucide-react";

interface DiscussionCardProps {
discussion: Discussion;
onUpvote: (id: string) => void;
onReply: (discussionId: string, content: string) => void;
onUpvoteReply: (discussionId: string, replyId: string) => void;
onMarkSolution: (discussionId: string, replyId: string) => void;
}

export default function DiscussionCard({
discussion,
onUpvote,
onReply,
onUpvoteReply,
onMarkSolution,
}: DiscussionCardProps) {
const [isExpanded, setIsExpanded] = useState(false);

return (
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-200 mb-4">
<div className="flex justify-between items-start mb-3">
<div>
<div className="flex items-center gap-2 mb-2">
{discussion.isSolved && (
<div className="flex items-center gap-1 text-green-600 font-medium">
<CheckCircle2 size={18} />
<span>Solved</span>
</div>
)}
<h3 className="text-xl font-bold text-[#1B0D00]">{discussion.title}</h3>
</div>
<div className="flex items-center gap-4 text-sm text-gray-500 mb-3">
<div className="flex items-center gap-1">
<span className="font-medium text-[#1B0D00]">{discussion.author}</span>
</div>
<div className="flex items-center gap-1">
<Clock size={14} />
{discussion.createdAt.toLocaleDateString()}
</div>
</div>
</div>
</div>

<p className="text-gray-700 mb-4">{discussion.content}</p>

<div className="flex gap-4 mb-4">
<button
onClick={() => onUpvote(discussion.id)}
className="flex items-center gap-2 text-gray-600 hover:text-[#d2b48c] transition-colors"
>
<ThumbsUp size={18} />
<span className="font-medium">{discussion.upvotes}</span>
</button>
<div className="flex items-center gap-2 text-gray-600">
<MessageSquare size={18} />
<span className="font-medium">{discussion.replies.length}</span>
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="flex items-center gap-2 text-gray-600 hover:text-[#d2b48c] transition-colors ml-auto"
>
{isExpanded ? <ChevronUp size={18} /> : <ChevronDown size={18} />}
{isExpanded ? "Hide Replies" : "View Replies"}
</button>
</div>

{isExpanded && (
<ReplySection
discussion={discussion}
onReply={onReply}
onUpvoteReply={onUpvoteReply}
onMarkSolution={onMarkSolution}
/>
)}
</div>
);
}

43 changes: 43 additions & 0 deletions app/components/discussions/DiscussionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

import { Discussion } from "./types";
import DiscussionCard from "./DiscussionCard";

interface DiscussionListProps {
discussions: Discussion[];
onUpvote: (id: string) => void;
onReply: (discussionId: string, content: string) => void;
onUpvoteReply: (discussionId: string, replyId: string) => void;
onMarkSolution: (discussionId: string, replyId: string) => void;
}

export default function DiscussionList({
discussions,
onUpvote,
onReply,
onUpvoteReply,
onMarkSolution,
}: DiscussionListProps) {
if (discussions.length === 0) {
return (
<div className="text-center py-12">
<p className="text-gray-500 text-lg">No discussions yet. Be the first to start one!</p>
</div>
);
}

return (
<div>
{discussions.map((discussion) => (
<DiscussionCard
key={discussion.id}
discussion={discussion}
onUpvote={onUpvote}
onReply={onReply}
onUpvoteReply={onUpvoteReply}
onMarkSolution={onMarkSolution}
/>
))}
</div>
);
}

58 changes: 58 additions & 0 deletions app/components/discussions/NewDiscussionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

"use client";
import { useState } from "react";
import { Send } from "lucide-react";

interface NewDiscussionFormProps {
onSubmit: (title: string, content: string) => void;
}

export default function NewDiscussionForm({ onSubmit }: NewDiscussionFormProps) {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (title.trim() && content.trim()) {
onSubmit(title.trim(), content.trim());
setTitle("");
setContent("");
}
};

return (
<div className="bg-white rounded-xl shadow-md p-6 mb-8 border border-gray-200">
<h3 className="text-xl font-bold text-[#1B0D00] mb-4">Start a New Discussion</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="What's your question or topic?"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#d2b48c] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Description</label>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Provide more details about your discussion..."
rows={5}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#d2b48c] focus:border-transparent resize-none"
/>
</div>
<button
type="submit"
className="flex items-center gap-2 bg-[#1B0D00] text-white px-6 py-3 rounded-lg hover:bg-[#2a1500] transition-colors font-medium"
>
<Send size={20} />
Post Discussion
</button>
</form>
</div>
);
}

100 changes: 100 additions & 0 deletions app/components/discussions/ReplySection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@

"use client";
import { useState } from "react";
import { Reply, Discussion } from "./types";
import { Send, ThumbsUp, CheckCircle2 } from "lucide-react";

interface ReplySectionProps {
discussion: Discussion;
onReply: (discussionId: string, content: string) => void;
onUpvoteReply: (discussionId: string, replyId: string) => void;
onMarkSolution: (discussionId: string, replyId: string) => void;
}

export default function ReplySection({
discussion,
onReply,
onUpvoteReply,
onMarkSolution,
}: ReplySectionProps) {
const [replyContent, setReplyContent] = useState("");

const handleSubmitReply = (e: React.FormEvent) => {
e.preventDefault();
if (replyContent.trim()) {
onReply(discussion.id, replyContent.trim());
setReplyContent("");
}
};

return (
<div className="mt-6 pt-6 border-t border-gray-200">
<h4 className="text-lg font-semibold text-[#1B0D00] mb-4">
{discussion.replies.length} {discussion.replies.length === 1 ? "Reply" : "Replies"}
</h4>

<div className="space-y-4 mb-6">
{discussion.replies.map((reply) => (
<div
key={reply.id}
className={`p-4 rounded-lg border ${
reply.isSolution
? "bg-green-50 border-green-300"
: "bg-gray-50 border-gray-200"
}`}
>
{reply.isSolution && (
<div className="flex items-center gap-2 text-green-700 font-medium mb-2">
<CheckCircle2 size={18} />
<span>Marked as Solution</span>
</div>
)}
<div className="flex justify-between items-start mb-2">
<div className="font-medium text-[#1B0D00]">{reply.author}</div>
<div className="text-sm text-gray-500">
{reply.createdAt.toLocaleDateString()}
</div>
</div>
<p className="text-gray-700 mb-3">{reply.content}</p>
<div className="flex gap-4">
<button
onClick={() => onUpvoteReply(discussion.id, reply.id)}
className="flex items-center gap-1 text-sm text-gray-600 hover:text-[#d2b48c] transition-colors"
>
<ThumbsUp size={16} />
{reply.upvotes}
</button>
{!reply.isSolution && !discussion.isSolved && (
<button
onClick={() => onMarkSolution(discussion.id, reply.id)}
className="flex items-center gap-1 text-sm text-gray-600 hover:text-green-600 transition-colors"
>
<CheckCircle2 size={16} />
Mark as Solution
</button>
)}
</div>
</div>
))}
</div>

<form onSubmit={handleSubmitReply} className="flex gap-3">
<textarea
value={replyContent}
onChange={(e) => setReplyContent(e.target.value)}
placeholder="Write a reply..."
rows={2}
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#d2b48c] focus:border-transparent resize-none"
/>
<button
type="submit"
className="self-end bg-[#d2b48c] text-[#1B0D00] px-4 py-2 rounded-lg hover:bg-[#c1a37b] transition-colors font-medium flex items-center gap-2"
>
<Send size={18} />
Reply
</button>
</form>
</div>
);
}

21 changes: 21 additions & 0 deletions app/components/discussions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

export interface Reply {
id: string;
author: string;
content: string;
createdAt: Date;
upvotes: number;
isSolution: boolean;
}

export interface Discussion {
id: string;
title: string;
author: string;
content: string;
createdAt: Date;
replies: Reply[];
isSolved: boolean;
upvotes: number;
}

10 changes: 10 additions & 0 deletions app/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export default function Navbar() {
<li>
<Link href="/quiz" className="hover:text-[#d2b48c] transition-colors duration-200 cursor-pointer">QUIZ</Link>
</li>
<li>
<Link href="/discussions" className="hover:text-[#d2b48c] transition-colors duration-200 cursor-pointer">
DISCUSSIONS
</Link>
</li>
<li>
<Link href="/bookmarks" className="hover:opacity-80 transition-opacity">
BOOKMARKS
Expand Down Expand Up @@ -122,6 +127,11 @@ export default function Navbar() {
QUIZ
</Link>
</li>
<li>
<Link href="/discussions" onClick={() => setMenuOpen(false)} className="hover:text-[#d2b48c] transition-colors duration-200 cursor-pointer">
DISCUSSIONS
</Link>
</li>
</ul>
</div>
</nav>
Expand Down
Loading