tahamajs/IE / Projects /front /src /components /collections /CollectionCard.tsx
tahamajs's picture
download
raw
3.73 kB
import { useNavigate } from "react-router-dom";
import { Folder, Trash2, Lock, Globe, Loader2 } from "lucide-react";
import { toast } from "react-toastify";
interface Collection {
id: string;
name: string;
description?: string;
articleCount?: number;
paperCount?: number;
isPublic: boolean;
createdAt: string;
updatedAt: string;
ownerUsername: string;
}
interface Props {
collection: Collection;
onDelete: (id: string) => Promise<void> | void;
isDeleting?: boolean;
}
export function CollectionCard({ collection, onDelete, isDeleting }: Props) {
const navigate = useNavigate();
const handleCardClick = () => {
navigate(`/collections/${collection.id}`);
};
const handleDelete = async (e: React.MouseEvent) => {
e.stopPropagation();
if (!confirm(`Are you sure you want to delete "${collection.name}"?`)) {
return;
}
try {
await onDelete(collection.id);
toast.success("Collection deleted successfully");
} catch (error) {
console.error("Delete error:", error);
toast.error("Failed to delete collection");
}
};
return (
<div
onClick={handleCardClick}
className="group relative cursor-pointer rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-5 transition-all duration-200 hover:shadow-lg hover:border-primary/50 hover:-translate-y-1"
>
<div className="flex items-start justify-between">
{/* Icon */}
<div className="p-2 rounded-lg bg-primary/10">
<Folder className="w-6 h-6 text-primary" />
</div>
{/* Delete Button */}
<button
onClick={handleDelete}
disabled={isDeleting}
className="opacity-0 group-hover:opacity-100 transition-opacity p-1.5 rounded-lg text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 disabled:opacity-50"
title="Delete collection"
>
{isDeleting ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</button>
</div>
{/* Title */}
<h3 className="mt-3 line-clamp-1 text-lg font-semibold text-gray-900 dark:text-gray-100">
{collection.name || "Untitled Collection"}
</h3>
{/* Description */}
{collection.description && (
<p className="mt-1 line-clamp-2 text-sm text-gray-500 dark:text-gray-400">
{collection.description}
</p>
)}
{/* Stats */}
<div className="mt-4 flex items-center justify-between text-xs">
<span className="text-gray-500 dark:text-gray-400">
{(collection.paperCount ?? collection.articleCount ?? 0)} paper{(collection.paperCount ?? collection.articleCount ?? 0) !== 1 ? "s" : ""}
</span>
{/* Privacy Badge */}
<div className="flex items-center gap-1.5">
{collection.isPublic ? (
<>
<Globe className="w-3 h-3 text-green-500" />
<span className="text-green-600 dark:text-green-400">Public</span>
</>
) : (
<>
<Lock className="w-3 h-3 text-amber-500" />
<span className="text-amber-600 dark:text-amber-400">Private</span>
</>
)}
</div>
</div>
{/* Created Date */}
<div className="mt-2 text-xs text-gray-400 dark:text-gray-500">
Created {new Date(collection.createdAt).toLocaleDateString()}
</div>
{/* Hover Effect Border */}
<div className="absolute inset-0 rounded-xl border-2 border-transparent group-hover:border-primary/20 pointer-events-none transition-colors" />
</div>
);
}

Xet Storage Details

Size:
3.73 kB
·
Xet hash:
cb19f26dfe29b0035d20c1cfd9504df958ee097c6ef659956baee756f60b8bb3

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.