Files
pragativeer-v2/src/app/dashboard/community/page.tsx

362 lines
18 KiB
TypeScript

"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
import { MessageSquare, ThumbsUp, ChevronUp, Search, Image as ImageIcon, CheckCircle, Lock, X, Send } from "lucide-react"
import { useActiveUser, isDemoUser } from "@/hooks/useActiveUser"
import { PremiumGate } from "@/components/PremiumGate"
interface Doubt {
id: string
author: string
avatar: string
timeAgo: string
subject: string
topic: string
title: string
content: string
upvotes: number
replies: number
isSolved: boolean
hasModeratorReply: boolean
}
export default function CommunityPage() {
const { user, hasAccessTo, loading } = useActiveUser()
const isDemo = isDemoUser(user?.id)
const currentUserName = user?.name || (isDemo ? "Neha Verma" : "Student")
const currentUserInitials = currentUserName.substring(0, 2).toUpperCase()
const [activeFilter, setActiveFilter] = useState<'all' | 'my-doubts' | 'solved' | 'unsolved'>('all')
const [searchQuery, setSearchQuery] = useState('')
const [isAskModalOpen, setIsAskModalOpen] = useState(false)
const [expandedDoubtId, setExpandedDoubtId] = useState<string | null>(null)
const mockDoubts: Doubt[] = [
{
id: "1",
author: "Rahul Verma",
avatar: "RV",
timeAgo: "2 hours ago",
subject: "Physics",
topic: "Rotational Mechanics",
title: "Why does friction act forwards in a rolling wheel?",
content: "I'm confused about the direction of friction when a car accelerates forward. My book says friction pushes the wheel forward, but shouldn't it oppose motion?",
upvotes: 42,
replies: 5,
isSolved: true,
hasModeratorReply: true
},
{
id: "2",
author: "Priya Sharma",
avatar: "PS",
timeAgo: "5 hours ago",
subject: "Chemistry",
topic: "Chemical Kinetics",
title: "How to quickly identify a pseudo first-order reaction?",
content: "Are there any tricks to spot these in MCQs? I always end up wasting 2 minutes trying to derive the rate law.",
upvotes: 18,
replies: 2,
isSolved: false,
hasModeratorReply: false
},
...(isDemo ? [{
id: "3",
author: currentUserName,
avatar: currentUserInitials,
timeAgo: "1 day ago",
subject: "Mathematics",
topic: "Definite Integration",
title: "Applying King's Rule when limits are -a to a",
content: "I keep messing up the signs when applying the property f(x) + f(-x). Can someone show a step-by-step for the modulus function?",
upvotes: 12,
replies: 8,
isSolved: true,
hasModeratorReply: true
}] : [])
]
const filteredDoubts = mockDoubts.filter(d => {
if (activeFilter === 'my-doubts' && d.author !== currentUserName) return false
if (activeFilter === 'solved' && !d.isSolved) return false
if (activeFilter === 'unsolved' && d.isSolved) return false
if (searchQuery && !d.title.toLowerCase().includes(searchQuery.toLowerCase())) return false
return true
})
if (!hasAccessTo("doubt-resolution-community") && !isDemo) {
return (
<div className="p-8 space-y-8 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 animate-section animate-section-1">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-yellow-50 dark:bg-[var(--cyan-ai-10)] border border-yellow-300/60 dark:border-[var(--cyan-ai-20)]">
<MessageSquare className="text-yellow-600 dark:text-[var(--cyan-ai)]" size={28} />
</span>
Doubt Forum
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Ask questions, share knowledge, and learn together with the community.
</p>
</div>
</div>
<div className="p-12 text-center max-w-2xl mx-auto bg-secondary/20 border border-border rounded-3xl space-y-6 animate-section animate-section-2">
<div className="size-20 rounded-full bg-red-500/10 flex items-center justify-center mx-auto text-red-500">
<Lock size={40} />
</div>
<h2 className="text-2xl font-bold">Premium Feature</h2>
<p className="text-muted-foreground">
The Doubt Forum is a premium feature. Upgrade your plan to ask doubts and interact with tutors and the community.
</p>
<button
className="px-8 py-3 bg-[var(--cyan-ai)] text-black font-bold rounded-xl shadow-[0_0_20px_-5px_var(--cyan-ai)]"
onClick={() => window.location.href = '/upgrade'}
>
Upgrade Plan
</button>
</div>
</div>
)
}
return (
<div className="p-8 space-y-8 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4 animate-section animate-section-1">
<div>
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
<span className="p-2.5 rounded-xl bg-yellow-50 dark:bg-[var(--cyan-ai-10)] border border-yellow-300/60 dark:border-[var(--cyan-ai-20)]">
<MessageSquare className="text-yellow-600 dark:text-[var(--cyan-ai)]" size={28} />
</span>
Doubt Forum
</h1>
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
Ask questions, share knowledge, and learn together with the community.
</p>
</div>
<button
onClick={() => setIsAskModalOpen(true)}
className="px-5 py-2.5 bg-[var(--cyan-ai)] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(12,96,96,0.4)] hover:bg-[#00d0dd] transition-colors"
>
Ask a Doubt
</button>
</div>
<div className="flex flex-col md:flex-row gap-4 animate-section animate-section-2">
<div className="relative flex-1">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search size={18} className="text-muted-foreground" />
</div>
<input
type="text"
placeholder="Search doubts by keyword..."
className="w-full pl-10 pr-4 py-2.5 bg-card border border-border rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai-10)]"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<div className="flex items-center gap-2 bg-secondary/50 p-1 rounded-xl border border-border shrink-0 overflow-x-auto">
<FilterButton active={activeFilter === 'all'} onClick={() => setActiveFilter('all')}>All Doubts</FilterButton>
<FilterButton active={activeFilter === 'my-doubts'} onClick={() => setActiveFilter('my-doubts')}>My Doubts</FilterButton>
<FilterButton active={activeFilter === 'solved'} onClick={() => setActiveFilter('solved')}>Solved</FilterButton>
<FilterButton active={activeFilter === 'unsolved'} onClick={() => setActiveFilter('unsolved')}>Unsolved</FilterButton>
</div>
</div>
<div className="space-y-4 animate-section animate-section-3">
{loading ? (
<div className="text-center py-12 text-muted-foreground animate-pulse">Loading doubts...</div>
) : (<>{filteredDoubts.map((doubt) => (
<Card key={doubt.id} className="border-border shadow-sm hover:border-[var(--cyan-ai-10)] transition-colors cursor-pointer">
<CardContent className="p-0 flex">
{/* Upvote Sidebar */}
<div className="w-16 bg-secondary/20 border-r border-border flex flex-col items-center py-4 gap-1">
<button className="p-1.5 text-muted-foreground hover:text-[var(--cyan-ai)] hover:bg-[var(--cyan-ai-10)] rounded-lg transition-colors">
<ChevronUp size={24} />
</button>
<span className="font-bold text-foreground">{doubt.upvotes}</span>
</div>
{/* Main Content */}
<div className="flex-1 p-5">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="text-xs font-bold px-2.5 py-1 bg-secondary text-foreground rounded-md">{doubt.subject}</span>
<span className="text-xs font-medium text-muted-foreground">• {doubt.topic}</span>
</div>
<div className="text-xs text-muted-foreground">{doubt.timeAgo}</div>
</div>
<h3 className="text-lg font-bold text-foreground mb-1">{doubt.title}</h3>
<p className="text-sm text-muted-foreground line-clamp-2 mb-4">{doubt.content}</p>
<div className="flex items-center justify-between mt-auto">
<div className="flex items-center gap-2">
<div className="h-6 w-6 rounded-full bg-secondary flex items-center justify-center text-[10px] font-bold">
{doubt.avatar}
</div>
<span className="text-xs font-medium text-foreground">{doubt.author}</span>
{doubt.hasModeratorReply && (
<span className="ml-2 flex items-center gap-1 text-[10px] font-bold text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded-sm border border-emerald-500/20">
<CheckCircle size={10} /> Expert Replied
</span>
)}
</div>
<div className="flex items-center gap-4">
{doubt.isSolved && (
<span className="text-xs font-bold text-emerald-500 flex items-center gap-1">
<CheckCircle size={14} /> Solved
</span>
)}
<button
onClick={() => setExpandedDoubtId(expandedDoubtId === doubt.id ? null : doubt.id)}
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors text-sm font-medium"
>
<MessageSquare size={16} />
{doubt.replies} Replies
</button>
</div>
</div>
{/* Expanded Replies Section */}
{expandedDoubtId === doubt.id && (
<div className="mt-6 pt-4 border-t border-border space-y-4">
<div className="flex gap-3">
<div className="h-8 w-8 shrink-0 rounded-full bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] flex items-center justify-center text-xs font-bold border border-[var(--cyan-ai-30)]">
AI
</div>
<div className="bg-secondary/40 rounded-2xl rounded-tl-none p-4 text-sm flex-1">
<div className="font-bold mb-1 flex items-center gap-2">
PragatiVeer AI
<span className="text-[10px] text-emerald-500 bg-emerald-500/10 px-1.5 py-0.5 rounded uppercase tracking-wider">Expert</span>
</div>
<p className="text-muted-foreground">
This is a great question! When the car accelerates, the engine applies a torque to the wheel, making it want to spin faster. If there were no friction, the bottom of the tire would slip backward relative to the road. Therefore, static friction acts in the forward direction on the tire to oppose this backward slipping motion.
</p>
</div>
</div>
<div className="flex gap-3 mt-4 items-center">
<input
type="text"
placeholder="Add a reply..."
className="flex-1 bg-background border border-border rounded-xl px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai-10)]"
/>
<button className="p-2 bg-[var(--cyan-ai)] text-black rounded-xl hover:bg-[#00d0dd] transition-colors">
<Send size={16} />
</button>
</div>
</div>
)}
</div>
</CardContent>
</Card>
))}
{filteredDoubts.length === 0 && (
<div className="text-center py-12 text-muted-foreground bg-secondary/10 border border-dashed border-border rounded-2xl p-6">
<MessageSquare size={48} className="mx-auto mb-3 opacity-30 text-[var(--cyan-ai)]" />
<p className="font-bold text-foreground mb-1">
{activeFilter === 'my-doubts' ? "No Doubts Asked Yet" : "No doubts found matching your criteria."}
</p>
<p className="text-xs text-muted-foreground max-w-sm mx-auto">
{activeFilter === 'my-doubts'
? "You haven't asked any doubts yet. Click 'Ask a Doubt' above to get answers from mentors and peers."
: "Try selecting another filter or clearing your search query."}
</p>
</div>
)}
</>)}
</div>
{/* Ask Doubt Modal */}
{isAskModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-background/80 backdrop-blur-sm">
<div className="bg-background border border-border w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200">
<div className="flex justify-between items-center p-6 border-b border-border bg-secondary/30">
<h2 className="text-2xl font-bold flex items-center gap-2">
<MessageSquare className="text-[var(--cyan-ai)]" /> Ask a Doubt
</h2>
<button
onClick={() => setIsAskModalOpen(false)}
className="text-muted-foreground hover:text-foreground hover:bg-secondary p-2 rounded-lg transition-colors"
>
<X size={20} />
</button>
</div>
<div className="p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Subject</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai-10)]">
<option>Physics</option>
<option>Chemistry</option>
<option>Mathematics</option>
<option>Biology</option>
</select>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Topic</label>
<input
type="text"
placeholder="e.g. Rotational Mechanics"
className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai-10)]"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Question Title</label>
<input
type="text"
placeholder="What exactly are you stuck on?"
className="w-full bg-background border border-border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai-10)]"
/>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-muted-foreground">Detailed Description</label>
<textarea
rows={4}
placeholder="Explain your approach, what you've tried, and where you're getting stuck..."
className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai-10)] resize-none"
/>
</div>
<div className="pt-2 border-t border-border flex justify-between items-center">
<button className="flex items-center gap-2 text-muted-foreground hover:text-[var(--cyan-ai)] transition-colors text-sm font-medium px-3 py-2 rounded-lg hover:bg-secondary">
<ImageIcon size={18} /> Attach Image
</button>
<button
onClick={() => setIsAskModalOpen(false)}
className="flex items-center gap-2 px-6 py-2.5 bg-[var(--cyan-ai)] text-black font-bold rounded-xl shadow-[0_0_15px_-3px_rgba(12,96,96,0.4)] hover:bg-[#00d0dd] transition-colors"
>
Post Doubt <Send size={16} />
</button>
</div>
</div>
</div>
</div>
)}
</div>
)
}
function FilterButton({ children, active, onClick }: { children: React.ReactNode, active: boolean, onClick: () => void }) {
return (
<button
onClick={onClick}
className={`px-4 py-1.5 rounded-lg text-sm font-medium transition-colors whitespace-nowrap ${
active
? 'bg-background shadow-sm border border-border text-foreground'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{children}
</button>
)
}