2026-01-07 09:31:02 +00:00
|
|
|
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';
|
2026-01-13 18:47:57 +00:00
|
|
|
import {
|
|
|
|
|
Heart,
|
|
|
|
|
MessageSquare,
|
|
|
|
|
Repeat,
|
|
|
|
|
Share2,
|
|
|
|
|
MoreHorizontal,
|
|
|
|
|
Play,
|
|
|
|
|
} from 'lucide-react';
|
2026-01-07 09:31:02 +00:00
|
|
|
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);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
// Mock comments
|
|
|
|
|
const comments: Comment[] = post.recentComments || [
|
2026-01-13 18:47:57 +00:00
|
|
|
{
|
|
|
|
|
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: [],
|
|
|
|
|
},
|
2026-01-07 09:31:02 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const handleLike = () => {
|
2026-01-13 18:47:57 +00:00
|
|
|
setIsLiked(!isLiked);
|
|
|
|
|
setLikesCount((prev) => (isLiked ? prev - 1 : prev + 1));
|
|
|
|
|
if (!isLiked) addToast('Liked post');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleShareConfirm = (type: 'repost' | 'quote', _text?: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
addToast(type === 'repost' ? 'Reposted!' : 'Quote posted!', 'success');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2026-01-13 18:47:57 +00:00
|
|
|
<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 && (
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="px-4 pt-3 pb-0 flex items-center gap-2 text-xs text-kodo-content-dim font-bold uppercase tracking-wider">
|
2026-01-13 18:47:57 +00:00
|
|
|
<Repeat className="w-3 h-3" /> {post.repostAuthor} Reposted
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Post Header */}
|
|
|
|
|
<div className="p-4 flex items-start justify-between">
|
|
|
|
|
<div className="flex items-center gap-3">
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="w-10 h-10 rounded-full bg-kodo-steel overflow-hidden border border-kodo-steel cursor-pointer">
|
2026-01-13 18:47:57 +00:00
|
|
|
<img
|
|
|
|
|
src={post.author.avatar}
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
/>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<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"
|
|
|
|
|
/>
|
2026-01-07 09:31:02 +00:00
|
|
|
)}
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="text-xs text-kodo-content-dim">
|
2026-01-13 18:47:57 +00:00
|
|
|
{post.author.handle} • {post.timestamp}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
<Button variant="ghost" size="sm">
|
|
|
|
|
<MoreHorizontal className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Content */}
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="px-4 pb-2 text-kodo-text-main whitespace-pre-wrap leading-relaxed text-sm">
|
2026-01-13 18:47:57 +00:00
|
|
|
{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>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* 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}
|
aesthetic-improvements: remove excessive hover effects from high-priority files
- Removed scale transforms (hover:scale-[1.02], hover:scale-110, group-hover:scale-110/105) from cards and images
- Removed decorative shadow/glow effects (hover:shadow-neon-cyan/20, hover:shadow-lg) from cards
- Removed hover-lift class (translateY + shadow) from base Card and Button components
- Replaced excessive effects with subtle hover:bg-white/5 or hover:opacity-90
- Preserved functional hover states (group-hover:opacity-100 for play overlays, hover:bg-accent/50 for interactive feedback)
- Updated 14 files: ProductCard, TrackCard, CourseCard, EquipmentCard, PostCard, ProfileView, card.tsx, SearchPage, PlayerControls, OrderSummary, DiscoverView, PlaylistCard, Sidebar, button.tsx
- Effects are now subtle and purposeful, aligning with Surgical Minimalism
- Action 11.4.1.3 complete
2026-01-16 09:34:41 +00:00
|
|
|
className="w-full h-full object-cover"
|
2026-01-13 18:47:57 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{post.type === 'audio' && post.audioTrack && (
|
|
|
|
|
<div className="px-4 py-2">
|
aesthetic-improvements: replace secondary cyan hover states with steel (batch 2)
- Card components: CartItem, WishlistView, PostCard, GroupCard, PlaylistsView, UserCard (7 files)
- Settings components: BackupsView, SessionManagement, CloudIntegrationView, OfflineQueueManager (4 files)
- DashboardPage: stat cards and activity items hover states (2 instances)
- FeedView: input and button hover states (2 instances)
- Upload zones: MetadataForm, CreatePlaylistModal, CreateProductView, AddEquipmentView, CreateGroupModal (5 files, 6 instances)
- UserCard: avatar border hover state
- Total: ~18 files, ~20 instances replaced
- Preserved: Focus rings (cyan), active/selected states (cyan)
- Action 11.3.1.2 in progress (second batch complete)
2026-01-16 09:53:34 +00:00
|
|
|
<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">
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="w-12 h-12 bg-kodo-graphite rounded overflow-hidden relative group cursor-pointer">
|
2026-01-13 18:47:57 +00:00
|
|
|
<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" />
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="text-sm font-bold text-white truncate">
|
|
|
|
|
{post.audioTrack.title}
|
|
|
|
|
</div>
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="text-xs text-kodo-content-dim truncate">
|
2026-01-13 18:47:57 +00:00
|
|
|
{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>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{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>
|
2026-01-16 00:56:30 +00:00
|
|
|
<span className="text-xs text-kodo-content-dim">{opt.votes}%</span>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
))}
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="text-xs text-kodo-content-dim px-1">
|
2026-01-13 18:47:57 +00:00
|
|
|
Total votes: 124 • 2 days left
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Footer Actions */}
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="p-4 border-t border-kodo-steel flex items-center justify-between text-kodo-content-dim text-sm">
|
2026-01-13 18:47:57 +00:00
|
|
|
<button
|
|
|
|
|
className={`flex items-center gap-2 hover:text-kodo-magenta transition-colors group ${isLiked ? 'text-kodo-magenta' : ''}`}
|
|
|
|
|
onClick={handleLike}
|
|
|
|
|
>
|
|
|
|
|
<Heart
|
aesthetic-improvements: remove excessive hover effects from high-priority files
- Removed scale transforms (hover:scale-[1.02], hover:scale-110, group-hover:scale-110/105) from cards and images
- Removed decorative shadow/glow effects (hover:shadow-neon-cyan/20, hover:shadow-lg) from cards
- Removed hover-lift class (translateY + shadow) from base Card and Button components
- Replaced excessive effects with subtle hover:bg-white/5 or hover:opacity-90
- Preserved functional hover states (group-hover:opacity-100 for play overlays, hover:bg-accent/50 for interactive feedback)
- Updated 14 files: ProductCard, TrackCard, CourseCard, EquipmentCard, PostCard, ProfileView, card.tsx, SearchPage, PlayerControls, OrderSummary, DiscoverView, PlaylistCard, Sidebar, button.tsx
- Effects are now subtle and purposeful, aligning with Surgical Minimalism
- Action 11.4.1.3 complete
2026-01-16 09:34:41 +00:00
|
|
|
className={`w-5 h-5 ${isLiked ? 'fill-current' : ''}`}
|
2026-01-07 09:31:02 +00:00
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
<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 && (
|
consistency: remove unused button variants (neon, glass, premium, link)
- Removed neon, glass, premium, and link variants from Button component
- Replaced variant="link" in PostCard with variant="ghost" (with underline)
- Replaced variant="premium" in LibraryPage and FAB with variant="default"
- Updated COMPONENT_USAGE.md to reflect removed variants
- Remaining variants: default, destructive, outline, secondary, ghost
- Action 9.3.1.2 complete
2026-01-16 01:13:51 +00:00
|
|
|
<Button variant="ghost" size="sm" className="w-full text-xs mt-4 text-kodo-cyan hover:underline">
|
2026-01-13 18:47:57 +00:00
|
|
|
View all {post.comments} comments
|
consistency: replace custom buttons with Button component (partial)
- Replaced custom button implementations with Button component in 14 files
- Files updated: LiveStreamDetailView, DashboardPage, CommentItem, PostCard, SocialPage, SocialView, AdminUsersView, UserTableRow, ProjectsManager, CloudFileBrowser, FileManagerView, CreatorModal, ImageCropper, BulkUploadModal
- ~31 buttons replaced across high-priority files
- Used appropriate Button variants: ghost, outline, default, secondary, link
- Preserved visual appearance with className overrides where needed
- Action 9.2.1.2 in progress (partial completion)
2026-01-16 01:06:14 +00:00
|
|
|
</Button>
|
2026-01-13 18:47:57 +00:00
|
|
|
)}
|
|
|
|
|
<div className="mt-4 flex gap-3">
|
2026-01-16 00:56:30 +00:00
|
|
|
<div className="w-8 h-8 rounded-full bg-kodo-steel overflow-hidden flex-shrink-0">
|
2026-01-13 18:47:57 +00:00
|
|
|
<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>
|
2026-01-07 09:31:02 +00:00
|
|
|
)}
|
2026-01-13 18:47:57 +00:00
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{showShareModal && (
|
|
|
|
|
<SharePostModal
|
|
|
|
|
post={post}
|
|
|
|
|
onClose={() => setShowShareModal(false)}
|
|
|
|
|
onConfirm={handleShareConfirm}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|