import React, { useState, useEffect } from 'react'; import type { Asset, Category } from '../../../types'; import { apiClient } from '../../../lib/api-client'; import { Plus, ToggleLeft, ToggleRight, Trash2, X, Sparkles, Folder, FileText, Tag, Link2 } from 'lucide-react'; export const AssetManagement: React.FC = () => { const [assets, setAssets] = useState([]); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); // Form Modal State const [isOpen, setIsOpen] = useState(false); const [title, setTitle] = useState(''); const [categoryId, setCategoryId] = useState('silicon'); const [subcategory, setSubcategory] = useState(''); const [description, setDescription] = useState(''); const [tagsInput, setTagsInput] = useState(''); const [thumbnailUrl, setThumbnailUrl] = useState(''); const [githubUrl, setGithubUrl] = useState(''); const [status, setStatus] = useState<'draft' | 'published'>('draft'); const [formError, setFormError] = useState(''); const [submitting, setSubmitting] = useState(false); const fetchAssets = async () => { setLoading(true); try { const response = await apiClient.get('/assets'); setAssets(response.data); } catch (err) { console.error(err); } finally { setLoading(false); } }; const fetchCategories = async () => { try { const response = await apiClient.get('/categories'); setCategories(response.data); // Pre-set subcategory default if (response.data.length > 0) { setSubcategory(response.data[0].subcategories[0]); } } catch (err) { console.error(err); } }; useEffect(() => { fetchAssets(); fetchCategories(); }, []); // Update subcategories when category changes in form useEffect(() => { const cat = categories.find(c => c.id === categoryId); if (cat && cat.subcategories.length > 0) { setSubcategory(cat.subcategories[0]); } }, [categoryId, categories]); const handleToggleStatus = async (asset: Asset) => { const updatedStatus = asset.status === 'published' ? 'draft' : 'published'; try { const response = await apiClient.put(`/assets/${asset.id}`, { ...asset, status: updatedStatus, }); setAssets(prev => prev.map(a => (a.id === asset.id ? response.data : a))); } catch (err) { console.error(err); } }; const handleDelete = async (id: string) => { if (!window.confirm('Are you sure you want to delete this asset?')) return; try { await apiClient.delete(`/assets/${id}`); setAssets(prev => prev.filter(a => a.id !== id)); } catch (err) { console.error(err); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setFormError(''); if (!title.trim() || !description.trim()) { setFormError('Title and description are required.'); return; } setSubmitting(true); try { // Parse tags const tags = tagsInput .split(',') .map(t => t.trim()) .filter(t => t.length > 0); const payload = { title, categoryId, subcategory, description, tags, thumbnailUrl: thumbnailUrl.trim() || undefined, githubUrl: githubUrl.trim() || undefined, status, author: 'Portal Admin', }; const response = await apiClient.post('/assets/new', payload); setAssets(prev => [response.data, ...prev]); // Reset Form & Close setTitle(''); setDescription(''); setTagsInput(''); setThumbnailUrl(''); setGithubUrl(''); setStatus('draft'); setIsOpen(false); } catch (err) { setFormError('Failed to create asset.'); } finally { setSubmitting(false); } }; return (

Resource & Asset Catalog

Publish silicon blocks, software utilities, or cloud infrastructure configurations.

{/* Asset Table */}
{loading ? ( ) : assets.length === 0 ? ( ) : ( assets.map(asset => ( )) )}
Asset Title / Category Tags Downloads Status Actions
Loading resources catalog...
No resources cataloged yet.

{asset.title}

{asset.categoryId} {asset.subcategory}
{asset.tags.map(t => ( {t} ))}
{asset.downloadsCount} {asset.status}
{/* Toggle Status Button */} {/* Delete Button */}
{/* Form Dialog Modal */} {isOpen && (

Catalog New Asset

Enter technical specifications and publish rules for the resource.

{formError && (
{formError}
)}
setTitle(e.target.value)} placeholder="e.g. RISC-V Cryptographic Processor Core" className="w-full rounded-lg border border-ink-200 px-3.5 py-2 text-sm focus:border-primary-500 focus:outline-none" required />