veza/apps/web/src/components/social/PostCard.tsx

271 lines
9.5 KiB
TypeScript
Raw Normal View History

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-kodo-content-dim 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-kodo-steel 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-kodo-content-dim">
{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-kodo-text-main 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"
/>
</div>
)}
{post.type === 'audio' && post.audioTrack && (
<div className="px-4 py-2">
<div className="bg-kodo-ink p-3 rounded-xl flex items-center gap-4 border border-kodo-steel hover:border-kodo-steel/50 transition-colors">
<div className="w-12 h-12 bg-kodo-graphite 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-kodo-content-dim 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-kodo-content-dim">{opt.votes}%</span>
</div>
</div>
))}
<div className="text-xs text-kodo-content-dim 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-kodo-content-dim 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 ${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 variant="ghost" size="sm" className="w-full text-xs mt-4 text-kodo-cyan hover:underline">
View all {post.comments} comments
</Button>
)}
<div className="mt-4 flex gap-3">
<div className="w-8 h-8 rounded-full bg-kodo-steel 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}
/>
)}
</>
);
};