189 lines
9.7 KiB
TypeScript
189 lines
9.7 KiB
TypeScript
|
|
|
||
|
|
import React, { useState } from 'react';
|
||
|
|
import { Card } from '../ui/card';
|
||
|
|
import { Button } from '../ui/button';
|
||
|
|
import { Badge } from '../ui/badge';
|
||
|
|
import { Post, Comment } from '../../types';
|
||
|
|
import { Heart, MessageSquare, Repeat, Share2, MoreHorizontal, Play } from 'lucide-react';
|
||
|
|
import { CommentItem } from './CommentItem';
|
||
|
|
import { SharePostModal } from './SharePostModal';
|
||
|
|
import { useToast } from '../../context/ToastContext';
|
||
|
|
|
||
|
|
interface PostCardProps {
|
||
|
|
post: Post;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const PostCard: React.FC<PostCardProps> = ({ post }) => {
|
||
|
|
const { addToast } = useToast();
|
||
|
|
const [isLiked, setIsLiked] = useState(post.isLiked || false);
|
||
|
|
const [likesCount, setLikesCount] = useState(post.likes);
|
||
|
|
const [showComments, setShowComments] = useState(false);
|
||
|
|
const [showShareModal, setShowShareModal] = useState(false);
|
||
|
|
|
||
|
|
// Mock comments
|
||
|
|
const comments: Comment[] = post.recentComments || [
|
||
|
|
{ id: 'c1', author: { name: 'Fan_01', handle: '@fan', avatar: 'https://picsum.photos/50' }, content: 'This is fire! 🔥', timestamp: '10m', likes: 2 },
|
||
|
|
{ id: 'c2', author: { name: 'Producer_X', handle: '@pro_x', avatar: 'https://picsum.photos/51' }, content: 'What snare is that?', timestamp: '30m', likes: 5, replies: [] }
|
||
|
|
];
|
||
|
|
|
||
|
|
const handleLike = () => {
|
||
|
|
setIsLiked(!isLiked);
|
||
|
|
setLikesCount(prev => isLiked ? prev - 1 : prev + 1);
|
||
|
|
if (!isLiked) addToast("Liked post");
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleShareConfirm = (type: 'repost' | 'quote', _text?: string) => {
|
||
|
|
addToast(type === 'repost' ? 'Reposted!' : 'Quote posted!', 'success');
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<Card variant="default" className="p-0 overflow-hidden border-transparent hover:border-kodo-steel/50 transition-all animate-fadeIn mb-4">
|
||
|
|
|
||
|
|
{/* Repost Header */}
|
||
|
|
{post.isRepost && (
|
||
|
|
<div className="px-4 pt-3 pb-0 flex items-center gap-2 text-xs text-gray-500 font-bold uppercase tracking-wider">
|
||
|
|
<Repeat className="w-3 h-3" /> {post.repostAuthor} Reposted
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Post Header */}
|
||
|
|
<div className="p-4 flex items-start justify-between">
|
||
|
|
<div className="flex items-center gap-3">
|
||
|
|
<div className="w-10 h-10 rounded-full bg-gray-700 overflow-hidden border border-kodo-steel cursor-pointer">
|
||
|
|
<img src={post.author.avatar} className="w-full h-full object-cover" />
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<div className="font-bold text-white flex items-center gap-1 cursor-pointer hover:underline">
|
||
|
|
{post.author.name}
|
||
|
|
{post.author.isVerified && <Badge label="PRO" variant="cyan" className="scale-75 origin-left" />}
|
||
|
|
</div>
|
||
|
|
<div className="text-xs text-gray-500">{post.author.handle} • {post.timestamp}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<Button variant="ghost" size="sm"><MoreHorizontal className="w-4 h-4" /></Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Content */}
|
||
|
|
<div className="px-4 pb-2 text-gray-200 whitespace-pre-wrap leading-relaxed text-sm">
|
||
|
|
{post.content}
|
||
|
|
{post.tags && (
|
||
|
|
<div className="mt-2 flex flex-wrap gap-2">
|
||
|
|
{post.tags.map(tag => (
|
||
|
|
<span key={tag} className="text-kodo-cyan hover:underline cursor-pointer text-xs">{tag}</span>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Media Rendering */}
|
||
|
|
{post.type === 'image' && post.image && (
|
||
|
|
<div className="mt-2 w-full max-h-96 bg-black flex items-center justify-center overflow-hidden cursor-pointer">
|
||
|
|
<img src={post.image} className="w-full h-full object-cover hover:scale-105 transition-transform duration-500" />
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{post.type === 'audio' && post.audioTrack && (
|
||
|
|
<div className="px-4 py-2">
|
||
|
|
<div className="bg-gradient-to-r from-kodo-ink to-kodo-slate p-3 rounded-xl flex items-center gap-4 border border-kodo-steel hover:border-kodo-cyan/50 transition-colors">
|
||
|
|
<div className="w-12 h-12 bg-gray-800 rounded overflow-hidden relative group cursor-pointer">
|
||
|
|
<img src={post.audioTrack.coverUrl} className="w-full h-full object-cover" />
|
||
|
|
<div className="absolute inset-0 bg-black/40 flex items-center justify-center">
|
||
|
|
<Play className="w-5 h-5 text-white fill-current" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="flex-1 min-w-0">
|
||
|
|
<div className="text-sm font-bold text-white truncate">{post.audioTrack.title}</div>
|
||
|
|
<div className="text-xs text-gray-400 truncate">{post.audioTrack.artist}</div>
|
||
|
|
<div className="h-6 flex items-center gap-0.5 mt-1 opacity-50">
|
||
|
|
{Array.from({length: 40}).map((_, i) => (
|
||
|
|
<div key={i} className="w-1 bg-kodo-cyan" style={{height: `${Math.random() * 100 }%`}}></div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{post.type === 'poll' && post.pollOptions && (
|
||
|
|
<div className="px-4 py-2 space-y-2">
|
||
|
|
{post.pollOptions.map((opt, i) => (
|
||
|
|
<div key={i} className="relative h-10 bg-kodo-slate rounded overflow-hidden cursor-pointer hover:bg-kodo-steel/50 transition-colors border border-kodo-steel">
|
||
|
|
<div className="absolute top-0 left-0 h-full bg-kodo-cyan/20" style={{width: `${opt.votes}%`}}></div>
|
||
|
|
<div className="absolute inset-0 flex items-center justify-between px-4">
|
||
|
|
<span className="text-sm font-bold text-white">{opt.label}</span>
|
||
|
|
<span className="text-xs text-gray-400">{opt.votes}%</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
<div className="text-xs text-gray-500 px-1">Total votes: 124 • 2 days left</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Footer Actions */}
|
||
|
|
<div className="p-4 border-t border-kodo-steel flex items-center justify-between text-gray-400 text-sm">
|
||
|
|
<button
|
||
|
|
className={`flex items-center gap-2 hover:text-kodo-magenta transition-colors group ${isLiked ? 'text-kodo-magenta' : ''}`}
|
||
|
|
onClick={handleLike}
|
||
|
|
>
|
||
|
|
<Heart className={`w-5 h-5 group-hover:scale-110 transition-transform ${isLiked ? 'fill-current' : ''}`} />
|
||
|
|
<span>{likesCount}</span>
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
className="flex items-center gap-2 hover:text-white transition-colors"
|
||
|
|
onClick={() => setShowComments(!showComments)}
|
||
|
|
>
|
||
|
|
<MessageSquare className="w-5 h-5" /> <span>{post.comments}</span>
|
||
|
|
</button>
|
||
|
|
<button className="flex items-center gap-2 hover:text-kodo-lime transition-colors" onClick={() => setShowShareModal(true)}>
|
||
|
|
<Repeat className="w-5 h-5" /> <span>{post.shares}</span>
|
||
|
|
</button>
|
||
|
|
<button className="flex items-center gap-2 hover:text-white transition-colors" onClick={() => setShowShareModal(true)}>
|
||
|
|
<Share2 className="w-5 h-5" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Comments Section */}
|
||
|
|
{showComments && (
|
||
|
|
<div className="bg-kodo-ink border-t border-kodo-steel p-4 animate-slideUp">
|
||
|
|
<div className="space-y-4">
|
||
|
|
{comments.map(c => (
|
||
|
|
<CommentItem
|
||
|
|
key={c.id}
|
||
|
|
comment={c}
|
||
|
|
onLike={(_id) => addToast("Liked comment")}
|
||
|
|
onReply={(handle) => addToast(`Replying to ${handle}`)}
|
||
|
|
/>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
{post.comments > 2 && (
|
||
|
|
<button className="w-full text-center text-xs text-kodo-cyan mt-4 hover:underline">
|
||
|
|
View all {post.comments} comments
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
<div className="mt-4 flex gap-3">
|
||
|
|
<div className="w-8 h-8 rounded-full bg-gray-700 overflow-hidden flex-shrink-0">
|
||
|
|
<img src="https://picsum.photos/id/100/100/100" className="w-full h-full object-cover" />
|
||
|
|
</div>
|
||
|
|
<div className="flex-1 relative">
|
||
|
|
<input
|
||
|
|
className="w-full bg-kodo-void border border-kodo-steel rounded-full px-4 py-2 text-sm text-white focus:border-kodo-cyan outline-none"
|
||
|
|
placeholder="Write a comment..."
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{showShareModal && (
|
||
|
|
<SharePostModal
|
||
|
|
post={post}
|
||
|
|
onClose={() => setShowShareModal(false)}
|
||
|
|
onConfirm={handleShareConfirm}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
};
|