import { useState, useEffect } from "react"; import { motion } from "framer-motion"; import type { Variants } from "framer-motion"; import { UploadCloud, Search, File, CheckCircle, Share2, Folder, Sparkles } from "lucide-react"; import { useAuthStore } from "../hooks/use-auth"; import { axiosInstance } from "../services/axios"; import { getAssets, getOrganizations, deleteAsset, requestDownload, approveDownloadRequest, rejectDownloadRequest, downloadAssetFile, getAssetGroups, } from "../services/assets-api"; import type { AssetGroup } 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 { ManageGroupsModal } from "../features/assets/components/ManageGroupsModal"; import { PageHeader } from "../components/ui/PageHeader"; import Button from "../components/ui/Button"; import { PageLayout } from "../components/layout/PageLayout"; import Modal from "../components/ui/Modal"; // 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); const [groups, setGroups] = useState([]); // Search & Filter const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState("ALL"); // Find recommended assets based on user's partnerGroup matching any AssetGroup.name (supports comma-separated multiple groups) const partnerGroupStrings = user?.partnerGroup && user.role === "PARTNER_USER" ? user.partnerGroup.split(',').map(s => s.trim().toLowerCase()) : []; // Only recommend assets that the partner actually has permission to access, and deduplicate const recommendedAssets = (() => { if (partnerGroupStrings.length === 0) return []; const activeGroups = groups.filter(g => partnerGroupStrings.includes(g.name.trim().toLowerCase())); return activeGroups .flatMap(g => g.assets) .filter((recAsset, index, self) => self.findIndex(a => a.id === recAsset.id) === index && assets.some(allAsset => allAsset.id === recAsset.id) ); })(); // 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 [isGroupsOpen, setIsGroupsOpen] = useState(false); const [activeAsset, setActiveAsset] = useState(null); const [activeMenuId, setActiveMenuId] = useState(null); const [selectedAssetIds, setSelectedAssetIds] = useState([]); const [expandedAssetId, setExpandedAssetId] = useState(null); const [assetToDelete, setAssetToDelete] = useState<{ id: string, title: string } | null>(null); useEffect(() => { fetchData(); }, []); useEffect(() => { if (selectedCategory !== "ALL" && selectedCategory !== "RECOMMENDED") { setSelectedCategory("ALL"); } }, [assets, selectedCategory]); const fetchData = async () => { setLoading(true); try { const assetsData = await getAssets(); setAssets(assetsData); setSelectedAssetIds([]); // Load groups for both ADMIN (for managing) and PARTNER (for recommendations) const groupsData = await getAssetGroups(); setGroups(groupsData); 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 = (id: string) => { const asset = assets.find(a => a.id === id); if (asset) { setAssetToDelete({ id: asset.id, title: asset.title }); } }; const confirmDeleteAsset = async () => { if (!assetToDelete) return; try { await deleteAsset(assetToDelete.id); success("Asset deleted successfully", `"${assetToDelete.title}" has been permanently removed.`); setAssetToDelete(null); 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 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" || (selectedCategory === "RECOMMENDED" && recommendedAssets.some((r) => r.id === asset.id)); 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 = (
{/* Search Bar with stable minimum width */}
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..." />
{/* Toggle Controls: All Assets vs Recommended */} {user?.role === "PARTNER_USER" && recommendedAssets.length > 0 && (
)}
{user?.role === "ADMIN" && pendingRequestsCount > 0 && ( )} {user?.role === "ADMIN" && selectedAssetIds.length > 0 && ( )} {user?.role === "ADMIN" && ( )} {user?.role === "ADMIN" && ( )}
); return (
{loading ? (
) : ( <> {selectedCategory === "RECOMMENDED" && (
{/* Background decorative elements */}
Curated Catalog {recommendedAssets.length} {recommendedAssets.length === 1 ? 'Asset' : 'Assets'}

Recommended for You

These resources have been hand-picked by our team to match your partner profile and accelerate your integration.

)} {filteredAssets.length === 0 ? (

No assets found

There are no assets matching your criteria.

) : ( {filteredAssets.map((asset) => ( setExpandedAssetId(expandedAssetId === asset.id ? null : asset.id)} isRecommended={recommendedAssets.some(r => r.id === asset.id)} /> ))} )} )}
{/* 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} /> setIsGroupsOpen(false)} assets={assets} groups={groups} onRefresh={fetchData} /> {/* Delete Confirmation Modal */} setAssetToDelete(null)} title="Delete Asset" subtitle="Confirm permanent resource deletion." size="sm" >

Are you sure you want to permanently delete "{assetToDelete?.title}"? This action cannot be undone.

); }; export default AssetsPage;