import { useState, useEffect } from "react"; import { motion } from "framer-motion"; import type { Variants } from "framer-motion"; import { UploadCloud, Search, File, CheckCircle, Share2 } from "lucide-react"; import { useAuthStore } from "../hooks/use-auth"; import { axiosInstance } from "../services/axios"; import { getAssets, getOrganizations, deleteAsset, requestDownload, approveDownloadRequest, rejectDownloadRequest, downloadAssetFile, } from "../services/assets-api"; import { useToast } from "../hooks/use-toast"; // Subcomponents import { AssetCard } from "../features/assets/components/AssetCard"; import { UploadAssetModal } from "../features/assets/components/UploadAssetModal"; import { EditAssetModal } from "../features/assets/components/EditAssetModal"; import { ShareAssetModal } from "../features/assets/components/ShareAssetModal"; import { AssetDetailsModal } from "../features/assets/components/AssetDetailsModal"; import { AssetViewerModal } from "../features/assets/components/AssetViewerModal"; import { DownloadRequestsModal } from "../features/assets/components/DownloadRequestsModal"; import { PageHeader } from "../components/ui/PageHeader"; import Button from "../components/ui/Button"; import { PageLayout } from "../components/layout/PageLayout"; // Type Definitions import type { Asset, Organization } from "../types/assets"; const containerVariants: Variants = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.05 } }, }; const itemVariants: Variants = { hidden: { opacity: 0, y: 15, scale: 0.98 }, show: { opacity: 1, y: 0, scale: 1, transition: { type: "spring", stiffness: 350, damping: 25 }, }, }; export const AssetsPage = () => { const { success, error } = useToast(); const user = useAuthStore((state) => state.user); const [assets, setAssets] = useState([]); const [organizations, setOrganizations] = useState([]); const [loading, setLoading] = useState(true); // Search & Filter const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState("ALL"); // Modal Control States const [isUploadOpen, setIsUploadOpen] = useState(false); const [isEditOpen, setIsEditOpen] = useState(false); const [isShareOpen, setIsShareOpen] = useState(false); const [isDetailsOpen, setIsDetailsOpen] = useState(false); const [isViewerOpen, setIsViewerOpen] = useState(false); const [isRequestsOpen, setIsRequestsOpen] = useState(false); const [activeAsset, setActiveAsset] = useState(null); const [activeMenuId, setActiveMenuId] = useState(null); const [selectedAssetIds, setSelectedAssetIds] = useState([]); useEffect(() => { fetchData(); }, []); const fetchData = async () => { setLoading(true); try { const assetsData = await getAssets(); setAssets(assetsData); setSelectedAssetIds([]); if (user?.role === "ADMIN") { const orgsData = await getOrganizations(); setOrganizations(orgsData); } } catch (err) { console.error("Failed to fetch assets data", err); } finally { setLoading(false); } }; const openEditModal = (asset: Asset) => { setActiveAsset(asset); setIsEditOpen(true); }; const openShareModal = (asset: Asset) => { setSelectedAssetIds([]); setActiveAsset(asset); setIsShareOpen(true); }; const handleToggleSelectAsset = (assetId: string) => { setSelectedAssetIds((prev) => prev.includes(assetId) ? prev.filter((id) => id !== assetId) : [...prev, assetId] ); }; const openViewerModal = (asset: Asset) => { setActiveAsset(asset); setIsViewerOpen(true); }; const openDetailsModal = (asset: Asset) => { setActiveAsset(asset); setIsDetailsOpen(true); }; const handleDeleteAsset = async (id: string) => { const asset = assets.find(a => a.id === id); const title = asset ? asset.title : "this asset"; if ( !window.confirm(`Are you sure you want to permanently delete "${title}"?`) ) return; try { await deleteAsset(id); success("Asset deleted successfully", `"${title}" has been permanently removed.`); await fetchData(); } catch (err: any) { console.error("Failed to delete asset", err); error("Failed to delete asset", err.response?.data?.error || "Something went wrong."); } }; const handleRequestDownload = async (asset: Asset) => { try { await requestDownload(asset.id); success("Download request submitted", "An administrator has been notified of your request."); await fetchData(); } catch (err: any) { console.error("Failed to request download access", err); error("Failed to submit request", err.response?.data?.error || "Something went wrong."); } }; const handleApproveRequest = async (assetId: string, requestId: string) => { try { await approveDownloadRequest(assetId, requestId); success("Download request approved", "The partner can now download this asset."); await fetchData(); } catch (err: any) { console.error("Failed to approve request", err); error("Failed to approve request", err.response?.data?.error || "Something went wrong."); } }; const handleRejectRequest = async (assetId: string, requestId: string) => { try { await rejectDownloadRequest(assetId, requestId); success("Download request rejected", "The access request was denied."); await fetchData(); } catch (err: any) { console.error("Failed to reject request", err); error("Failed to reject request", err.response?.data?.error || "Something went wrong."); } }; const handleDownload = async (asset: Asset) => { success("Download started", `Downloading "${asset.title}"...`); try { await downloadAssetFile(asset.id); const downloadUrl = asset.url.startsWith("http") ? asset.url : `${axiosInstance.defaults.baseURL?.replace("/api/v1", "")}${asset.url}`; const a = document.createElement("a"); a.href = downloadUrl; a.download = asset.title; a.target = "_blank"; document.body.appendChild(a); a.click(); document.body.removeChild(a); setAssets((prev) => prev.map((item) => item.id === asset.id ? { ...item, downloadsCount: item.downloadsCount + 1 } : item, ), ); } catch (err: any) { console.error("Failed to process download", err); error("Download failed", err.response?.data?.error || "Could not retrieve asset file."); } }; const CATEGORIES = [ "ALL", "Marketing", "Presentations", "Branding", "Resources", "Technical", ]; const filteredAssets = assets.filter((asset) => { const query = searchQuery.toLowerCase().trim(); const matchesSearch = !query || asset.title.toLowerCase().includes(query) || (asset.description && asset.description.toLowerCase().includes(query)) || (asset.categoryId && asset.categoryId.toLowerCase().includes(query)) || (asset.subcategory && asset.subcategory.toLowerCase().includes(query)) || (asset.githubUrl && asset.githubUrl.toLowerCase().includes(query)) || asset.type.toLowerCase().includes(query) || asset.tags.some((tag) => tag.toLowerCase().includes(query)); const matchesCategory = selectedCategory === "ALL" || asset.categoryId === selectedCategory; return matchesSearch && matchesCategory; }); const pendingRequestsCount = assets.reduce((acc, asset) => { return ( acc + (asset.downloadRequests?.filter((r) => r.status === "PENDING").length || 0) ); }, 0); // Header component const headerNode = ( Global CDN Active } /> ); // Toolbar component const toolbarNode = (
setSearchQuery(e.target.value)} className="w-full bg-ink-50 border border-ink-200 rounded-lg py-1.5 pl-9 pr-4 text-xs font-semibold text-ink-900 placeholder-ink-400 outline-none transition-all focus:border-ink-900/50 focus:ring-2 ring-ink-900/10 hover:border-ink-300 font-sans" placeholder="Search by title, desc, tag, category..." />
{CATEGORIES.map((cat) => ( ))}
{user?.role === "ADMIN" && pendingRequestsCount > 0 && ( )} {user?.role === "ADMIN" && selectedAssetIds.length > 0 && ( )} {user?.role === "ADMIN" && ( )}
); return (
{loading ? (
) : filteredAssets.length === 0 ? (

No assets found

There are no assets matching your criteria.

) : ( {filteredAssets.map((asset) => ( ))} )}
{/* Modals Container */} setIsUploadOpen(false)} onSuccess={fetchData} /> { setIsEditOpen(false); setActiveAsset(null); }} asset={activeAsset} onSuccess={fetchData} /> { setIsShareOpen(false); setActiveAsset(null); setSelectedAssetIds([]); }} asset={activeAsset} assetIds={selectedAssetIds} organizations={organizations} onSuccess={fetchData} /> { setIsDetailsOpen(false); setActiveAsset(null); }} asset={activeAsset} userRole={user?.role} /> { setIsViewerOpen(false); setActiveAsset(null); }} asset={activeAsset} user={user} onDownload={handleDownload} /> setIsRequestsOpen(false)} assets={assets} onApprove={handleApproveRequest} onReject={handleRejectRequest} /> ); }; export default AssetsPage;