Files
marketing-app/client/src/components/AssetCard.jsx
fahed 35d84b6bff Marketing Hub: RBAC, i18n (AR/EN), tasks overhaul, team/user merge, tutorial
Features:
- Full RBAC with 3 roles (superadmin/manager/contributor)
- Ownership tracking on posts, tasks, campaigns, projects
- Task system: assign to anyone, filter combobox, visibility scoping
- Team members merged into users table (single source of truth)
- Post thumbnails on kanban cards from attachments
- Publication link validation before publishing
- Interactive onboarding tutorial with Settings restart
- Full Arabic/English i18n with RTL layout support
- Language toggle in sidebar, IBM Plex Sans Arabic font
- Brand-based visibility filtering for non-superadmins
- Manager can only create contributors
- Profile completion flow for new users
- Cookie-based sessions (express-session + SQLite)
2026-02-08 20:46:58 +03:00

87 lines
3.1 KiB
JavaScript

import { format } from 'date-fns'
import { Image, FileText, Film, Music, File, Download } from 'lucide-react'
const typeIcons = {
image: Image,
document: FileText,
video: Film,
audio: Music,
}
const formatFileSize = (bytes) => {
if (!bytes) return '—'
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
export default function AssetCard({ asset, onClick }) {
const TypeIcon = typeIcons[asset.type] || File
const isImage = asset.type === 'image'
return (
<div
onClick={() => onClick?.(asset)}
className="bg-white rounded-xl border border-border overflow-hidden card-hover cursor-pointer group"
>
{/* Thumbnail */}
<div className="aspect-square bg-surface-tertiary flex items-center justify-center overflow-hidden relative">
{isImage && asset.url ? (
<img
src={asset.url}
alt={asset.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
onError={(e) => {
e.target.style.display = 'none'
e.target.nextSibling.style.display = 'flex'
}}
/>
) : null}
<div
className={`flex flex-col items-center justify-center gap-2 ${isImage && asset.url ? 'hidden' : ''}`}
style={{ display: isImage && asset.url ? 'none' : 'flex' }}
>
<TypeIcon className="w-10 h-10 text-text-tertiary" />
<span className="text-xs text-text-tertiary uppercase font-medium">
{asset.fileType || asset.type}
</span>
</div>
{/* Hover overlay */}
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors flex items-center justify-center">
<button className="opacity-0 group-hover:opacity-100 bg-white/90 backdrop-blur-sm rounded-full p-2 shadow-lg transition-opacity">
<Download className="w-4 h-4 text-text-primary" />
</button>
</div>
</div>
{/* Info */}
<div className="p-3">
<h5 className="text-sm font-medium text-text-primary truncate">{asset.name}</h5>
<div className="flex items-center justify-between mt-1.5">
<span className="text-xs text-text-tertiary">{formatFileSize(asset.size)}</span>
{asset.createdAt && (
<span className="text-xs text-text-tertiary">
{format(new Date(asset.createdAt), 'MMM d')}
</span>
)}
</div>
{asset.tags && asset.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{asset.tags.slice(0, 3).map((tag) => (
<span key={tag} className="text-[10px] px-1.5 py-0.5 rounded bg-surface-tertiary text-text-tertiary">
{tag}
</span>
))}
{asset.tags.length > 3 && (
<span className="text-[10px] px-1.5 py-0.5 text-text-tertiary">
+{asset.tags.length - 3}
</span>
)}
</div>
)}
</div>
</div>
)
}