208 lines
9.0 KiB
TypeScript
208 lines
9.0 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { X } from 'lucide-react';
|
|
import { updateAsset } from '../../../services/assets-api';
|
|
import type { Asset } from '../../../types/assets';
|
|
|
|
interface EditAssetModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
asset: Asset | null;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
export const EditAssetModal: React.FC<EditAssetModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
asset,
|
|
onSuccess
|
|
}) => {
|
|
const [editTitle, setEditTitle] = useState('');
|
|
const [editDescription, setEditDescription] = useState('');
|
|
const [editCategory, setEditCategory] = useState('Marketing');
|
|
const [editSubcategory, setEditSubcategory] = useState('');
|
|
const [editTags, setEditTags] = useState('');
|
|
const [editGithubUrl, setEditGithubUrl] = useState('');
|
|
const [editIsDownloadable, setEditIsDownloadable] = useState(true);
|
|
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (asset) {
|
|
setEditTitle(asset.title);
|
|
setEditDescription(asset.description || '');
|
|
setEditCategory(asset.categoryId || 'Marketing');
|
|
setEditSubcategory(asset.subcategory || '');
|
|
setEditTags(asset.tags.join(', '));
|
|
setEditGithubUrl(asset.githubUrl || '');
|
|
setEditIsDownloadable(asset.isDownloadable);
|
|
}
|
|
}, [asset]);
|
|
|
|
const handleEditSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!asset) return;
|
|
|
|
setIsSavingEdit(true);
|
|
try {
|
|
await updateAsset(asset.id, {
|
|
title: editTitle,
|
|
description: editDescription,
|
|
categoryId: editCategory,
|
|
subcategory: editSubcategory,
|
|
tags: editTags.split(',').map(t => t.trim()).filter(Boolean),
|
|
githubUrl: editGithubUrl,
|
|
isDownloadable: editIsDownloadable,
|
|
});
|
|
onSuccess();
|
|
onClose();
|
|
} catch (err) {
|
|
console.error('Failed to save asset details', err);
|
|
} finally {
|
|
setIsSavingEdit(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isOpen && asset && (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={onClose}
|
|
className="absolute inset-0 bg-ink-950/40 backdrop-blur-sm"
|
|
/>
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95, y: 10 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
|
className="relative bg-ink-0 border border-ink-200 rounded-2xl p-6 max-w-lg w-full max-h-[90vh] shadow-xl z-10 flex flex-col overflow-hidden"
|
|
>
|
|
<div className="flex justify-between items-center pb-3.5 border-b border-ink-100 flex-shrink-0">
|
|
<h3 className="text-lg font-bold text-ink-900">Edit Asset Details</h3>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-1 rounded-lg text-ink-400 hover:text-ink-900 hover:bg-ink-50 transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleEditSubmit} className="flex-1 min-h-0 flex flex-col mt-4">
|
|
<div className="flex-1 overflow-y-auto pr-1 space-y-4 scrollbar-thin">
|
|
<div>
|
|
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">Asset Title</label>
|
|
<input
|
|
type="text"
|
|
value={editTitle}
|
|
onChange={(e) => setEditTitle(e.target.value)}
|
|
required
|
|
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">Category</label>
|
|
<select
|
|
value={editCategory}
|
|
onChange={(e) => setEditCategory(e.target.value)}
|
|
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900"
|
|
>
|
|
<option value="Marketing">Marketing</option>
|
|
<option value="Presentations">Presentations</option>
|
|
<option value="Branding">Branding</option>
|
|
<option value="Resources">Resources</option>
|
|
<option value="Technical">Technical</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">Subcategory</label>
|
|
<input
|
|
type="text"
|
|
value={editSubcategory}
|
|
onChange={(e) => setEditSubcategory(e.target.value)}
|
|
placeholder="e.g. Slide Deck"
|
|
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">Description</label>
|
|
<textarea
|
|
value={editDescription}
|
|
onChange={(e) => setEditDescription(e.target.value)}
|
|
placeholder="Enter short description..."
|
|
rows={3}
|
|
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400 resize-none"
|
|
/>
|
|
</div>
|
|
|
|
{asset.type !== 'url' && (
|
|
<div className="flex items-center gap-3 p-3 bg-ink-50 border border-ink-200 rounded-lg">
|
|
<input
|
|
type="checkbox"
|
|
id="editIsDownloadable"
|
|
checked={editIsDownloadable}
|
|
onChange={(e) => setEditIsDownloadable(e.target.checked)}
|
|
className="w-4 h-4 rounded border-ink-300 text-ink-900 focus:ring-ink-900/10 cursor-pointer"
|
|
/>
|
|
<div>
|
|
<label htmlFor="editIsDownloadable" className="text-xs font-bold text-ink-900 cursor-pointer block">
|
|
Allow Direct Download (Strict View Only if unchecked)
|
|
</label>
|
|
<span className="text-[10px] text-ink-500">
|
|
Toggle client authorization requirement for asset downloads.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">Tags (comma-separated)</label>
|
|
<input
|
|
type="text"
|
|
value={editTags}
|
|
onChange={(e) => setEditTags(e.target.value)}
|
|
placeholder="branding, guideline, pitch"
|
|
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">GitHub/Documentation URL (Optional)</label>
|
|
<input
|
|
type="url"
|
|
value={editGithubUrl}
|
|
onChange={(e) => setEditGithubUrl(e.target.value)}
|
|
placeholder="https://github.com/..."
|
|
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="pt-3.5 border-t border-ink-100 flex justify-end gap-3 flex-shrink-0 mt-4">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="px-4 py-2 rounded-lg border border-ink-200 text-ink-700 text-xs font-bold hover:bg-ink-50 transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={isSavingEdit}
|
|
className="px-5 py-2 rounded-lg bg-ink-900 text-ink-0 text-xs font-bold hover:bg-ink-800 transition-colors shadow-sm disabled:opacity-50"
|
|
>
|
|
{isSavingEdit ? 'Saving...' : 'Save Changes'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</motion.div>
|
|
</div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
};
|