import React, { useState, useMemo, useRef, useCallback } from 'react' import { usePage, Link } from '@inertiajs/react' import StudioLayout from '../../Layouts/StudioLayout' import MarkdownEditor from '../../components/ui/MarkdownEditor' import TextInput from '../../components/ui/TextInput' import Button from '../../components/ui/Button' import Toggle from '../../components/ui/Toggle' import Modal from '../../components/ui/Modal' import FormField from '../../components/ui/FormField' import TagPicker from '../../components/tags/TagPicker' // ─── Helpers ───────────────────────────────────────────────────────────────── function getCsrfToken() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' } function formatBytes(bytes) { if (!bytes) return '—' if (bytes < 1024) return bytes + ' B' if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB' return (bytes / 1048576).toFixed(1) + ' MB' } function getContentTypeVisualKey(slug) { const map = { skins: 'skins', wallpapers: 'wallpapers', photography: 'photography', other: 'other', members: 'members' } return map[slug] || 'other' } function buildCategoryTree(contentTypes) { return (contentTypes || []).map((ct) => ({ ...ct, rootCategories: (ct.categories || ct.root_categories || []).map((rc) => ({ ...rc, children: rc.children || [], })), })) } // ─── Sub-components ────────────────────────────────────────────────────────── /** Glass-morphism section card (Nova theme) */ function Section({ children, className = '' }) { return (
{children}
) } /** Section heading */ function SectionTitle({ icon, children }) { return (

{icon && } {children}

) } // ─── Main Component ────────────────────────────────────────────────────────── export default function StudioArtworkEdit() { const { props } = usePage() const { artwork, contentTypes: rawContentTypes } = props const contentTypes = useMemo(() => buildCategoryTree(rawContentTypes || []), [rawContentTypes]) // ── State ────────────────────────────────────────────────────────────────── const [contentTypeId, setContentTypeId] = useState(artwork?.content_type_id || null) const [categoryId, setCategoryId] = useState(artwork?.parent_category_id || null) const [subCategoryId, setSubCategoryId] = useState(artwork?.sub_category_id || null) const [title, setTitle] = useState(artwork?.title || '') const [description, setDescription] = useState(artwork?.description || '') const [tagSlugs, setTagSlugs] = useState(() => (artwork?.tags || []).map((t) => t.slug || t.name)) const [isPublic, setIsPublic] = useState(artwork?.is_public ?? true) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) const [errors, setErrors] = useState({}) // File replace const fileInputRef = useRef(null) const [replacing, setReplacing] = useState(false) const [thumbUrl, setThumbUrl] = useState(artwork?.thumb_url_lg || artwork?.thumb_url || null) const [fileMeta, setFileMeta] = useState({ name: artwork?.file_name || '—', size: artwork?.file_size || 0, width: artwork?.width || 0, height: artwork?.height || 0, }) const [versionCount, setVersionCount] = useState(artwork?.version_count ?? 1) const [requiresReapproval, setRequiresReapproval] = useState(artwork?.requires_reapproval ?? false) const [changeNote, setChangeNote] = useState('') const [showChangeNote, setShowChangeNote] = useState(false) // Version history const [showHistory, setShowHistory] = useState(false) const [historyData, setHistoryData] = useState(null) const [historyLoading, setHistoryLoading] = useState(false) const [restoring, setRestoring] = useState(null) // ── Derived ──────────────────────────────────────────────────────────────── const selectedCT = contentTypes.find((ct) => ct.id === contentTypeId) || null const rootCategories = selectedCT?.rootCategories || [] const selectedRoot = rootCategories.find((c) => c.id === categoryId) || null const subCategories = selectedRoot?.children || [] // ── Handlers ─────────────────────────────────────────────────────────────── const handleContentTypeChange = (id) => { setContentTypeId(id) setCategoryId(null) setSubCategoryId(null) } const handleCategoryChange = (id) => { setCategoryId(id) setSubCategoryId(null) } const handleSave = useCallback(async () => { setSaving(true) setSaved(false) setErrors({}) try { const payload = { title, description, is_public: isPublic, category_id: subCategoryId || categoryId || null, tags: tagSlugs, } const res = await fetch(`/api/studio/artworks/${artwork.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, credentials: 'same-origin', body: JSON.stringify(payload), }) if (res.ok) { setSaved(true) setTimeout(() => setSaved(false), 3000) } else { const data = await res.json() if (data.errors) setErrors(data.errors) } } catch (err) { console.error('Save failed:', err) } finally { setSaving(false) } }, [title, description, isPublic, subCategoryId, categoryId, tagSlugs, artwork?.id]) const handleFileReplace = async (e) => { const file = e.target.files?.[0] if (!file) return setReplacing(true) try { const fd = new FormData() fd.append('file', file) if (changeNote.trim()) fd.append('change_note', changeNote.trim()) const res = await fetch(`/api/studio/artworks/${artwork.id}/replace-file`, { method: 'POST', headers: { Accept: 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, credentials: 'same-origin', body: fd, }) const data = await res.json() if (res.ok && data.thumb_url) { setThumbUrl(data.thumb_url) setFileMeta({ name: file.name, size: file.size, width: data.width || 0, height: data.height || 0 }) if (data.version_number) setVersionCount(data.version_number) if (typeof data.requires_reapproval !== 'undefined') setRequiresReapproval(data.requires_reapproval) setChangeNote('') setShowChangeNote(false) } else { alert(data.error || 'File replacement failed.') } } catch (err) { console.error('File replace failed:', err) } finally { setReplacing(false) if (fileInputRef.current) fileInputRef.current.value = '' } } const loadVersionHistory = async () => { setHistoryLoading(true) setShowHistory(true) try { const res = await fetch(`/api/studio/artworks/${artwork.id}/versions`, { headers: { Accept: 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, credentials: 'same-origin', }) setHistoryData(await res.json()) } catch (err) { console.error('Failed to load version history:', err) } finally { setHistoryLoading(false) } } const handleRestoreVersion = async (versionId) => { if (!window.confirm('Restore this version? A copy will become the new current version.')) return setRestoring(versionId) try { const res = await fetch(`/api/studio/artworks/${artwork.id}/restore/${versionId}`, { method: 'POST', headers: { Accept: 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, credentials: 'same-origin', }) const data = await res.json() if (res.ok && data.success) { setVersionCount((n) => n + 1) setShowHistory(false) } else { alert(data.error || 'Restore failed.') } } catch (err) { console.error('Restore failed:', err) } finally { setRestoring(null) } } // ── Render ───────────────────────────────────────────────────────────────── return ( {/* ── Page Header ── */}

{title || 'Untitled artwork'}

Editing ·{' '} {isPublic ? 'Published' : 'Draft'}

{saved && ( Saved )}
{/* ── Two-column Layout ── */}
{/* ─────────── LEFT SIDEBAR ─────────── */}
{/* Preview Card */}
Preview {/* Thumbnail */}
{thumbUrl ? ( {title ) : (
)} {replacing && (
)}
{/* File Metadata */}

{fileMeta.name}

{fileMeta.width > 0 && ( {fileMeta.width} × {fileMeta.height} )} {formatBytes(fileMeta.size)}
{/* Version + History */}
v{versionCount}
{requiresReapproval && (

Requires re-approval after replace

)}
{/* Replace File */}
{showChangeNote && ( setChangeNote(e.target.value)} placeholder="Change note (optional)…" size="sm" /> )}
{!showChangeNote && ( )}
{/* Quick Links */}
View Analytics
{/* ─────────── RIGHT MAIN FORM ─────────── */}
{/* ── Content Type ── */}
Content Type
{contentTypes.map((ct) => { const isActive = contentTypeId === ct.id const visualKey = getContentTypeVisualKey(ct.slug) return ( ) })}
{/* ── Category ── */} {rootCategories.length > 0 && (
Category
{rootCategories.map((cat) => { const isActive = categoryId === cat.id return ( ) })}
{subCategories.length > 0 && (

Subcategory

{subCategories.map((sub) => { const isActive = subCategoryId === sub.id return ( ) })}
)} {errors.category_id &&

{errors.category_id[0]}

}
)} {/* ── Details (Title + Description) ── */}
Details setTitle(e.target.value)} placeholder="Give your artwork a title" error={errors.title?.[0]} required />
{/* ── Tags ── */}
Tags
{/* ── Visibility ── */}
Visibility

{isPublic ? 'Your artwork is visible to everyone' : 'Your artwork is only visible to you'}

setIsPublic(e.target.checked)} label={isPublic ? 'Published' : 'Draft'} variant={isPublic ? 'emerald' : 'accent'} size="md" />
{/* ── Bottom Save Bar (mobile) ── */}
{saved && ( Saved )}
{/* ── Version History Modal ── */} setShowHistory(false)} title="Version History" size="lg" footer={

Restoring creates a new version — nothing is deleted.

} > {historyLoading && (
)} {!historyLoading && historyData && (
{historyData.versions.map((v) => (
v{v.version_number} {v.is_current && ( Current )}

{v.created_at ? new Date(v.created_at).toLocaleString() : ''}

{v.width && (

{v.width} × {v.height} px · {formatBytes(v.file_size)}

)} {v.change_note && (

“{v.change_note}”

)}
{!v.is_current && ( )}
))} {historyData.versions.length === 0 && (

No version history yet.

)}
)} ) }