import React, { useEffect, useMemo, useState } from 'react'
import UploadSidebar from '../UploadSidebar'
import { getContentTypeValue, getContentTypeVisualKey } from '../../../lib/uploadUtils'
/**
* Step2Details
*
* Step 2 of the upload wizard: artwork metadata.
* Shows uploaded-asset summary, content type selector,
* category/subcategory selectors, tags, description, and rights.
*/
export default function Step2Details({
headingRef,
// Asset summary
primaryFile,
primaryPreviewUrl,
isArchive,
fileMetadata,
screenshots,
// Content type + category
contentTypes,
metadata,
metadataErrors,
filteredCategoryTree,
allRootCategoryOptions,
requiresSubCategory,
onContentTypeChange,
onRootCategoryChange,
onSubCategoryChange,
// Sidebar (title / tags / description / rights)
suggestedTags,
onChangeTitle,
onChangeTags,
onChangeDescription,
onToggleMature,
onToggleRights,
}) {
const [isContentTypeChooserOpen, setIsContentTypeChooserOpen] = useState(() => !metadata.contentType)
const [isCategoryChooserOpen, setIsCategoryChooserOpen] = useState(() => !metadata.rootCategoryId)
const [isSubCategoryChooserOpen, setIsSubCategoryChooserOpen] = useState(() => !metadata.subCategoryId)
const [categorySearch, setCategorySearch] = useState('')
const [subCategorySearch, setSubCategorySearch] = useState('')
const contentTypeOptions = useMemo(
() => (Array.isArray(contentTypes) ? contentTypes : []).map((item) => {
const normalizedName = String(item?.name || '').trim().toLowerCase()
const normalizedSlug = String(item?.slug || '').trim().toLowerCase()
if (normalizedName === 'other' || normalizedSlug === 'other') {
return {
...item,
name: 'Others',
}
}
return item
}),
[contentTypes]
)
const selectedContentType = useMemo(
() => contentTypeOptions.find((item) => String(getContentTypeValue(item)) === String(metadata.contentType || '')) ?? null,
[contentTypeOptions, metadata.contentType]
)
const selectedRoot = useMemo(
() => filteredCategoryTree.find((item) => String(item.id) === String(metadata.rootCategoryId || '')) ?? null,
[filteredCategoryTree, metadata.rootCategoryId]
)
const subCategories = selectedRoot?.children || []
const selectedSubCategory = useMemo(
() => subCategories.find((item) => String(item.id) === String(metadata.subCategoryId || '')) ?? null,
[subCategories, metadata.subCategoryId]
)
const sortedFilteredCategories = useMemo(() => {
const sorted = [...filteredCategoryTree].sort((a, b) => a.name.localeCompare(b.name))
const q = categorySearch.trim().toLowerCase()
return q ? sorted.filter((c) => c.name.toLowerCase().includes(q)) : sorted
}, [filteredCategoryTree, categorySearch])
const sortedFilteredSubCategories = useMemo(() => {
const sorted = [...subCategories].sort((a, b) => a.name.localeCompare(b.name))
const q = subCategorySearch.trim().toLowerCase()
return q ? sorted.filter((s) => s.name.toLowerCase().includes(q)) : sorted
}, [subCategories, subCategorySearch])
useEffect(() => {
if (!metadata.contentType) {
setIsContentTypeChooserOpen(true)
}
}, [metadata.contentType])
useEffect(() => {
if (!metadata.rootCategoryId) {
setIsCategoryChooserOpen(true)
}
}, [metadata.rootCategoryId])
useEffect(() => {
if (!metadata.subCategoryId) {
setIsSubCategoryChooserOpen(true)
}
}, [metadata.subCategoryId])
return (
{/* Step header */}
Artwork details
Complete required metadata and rights confirmation before publishing.
{/* Uploaded asset summary */}
Uploaded asset
{/* Thumbnail / Archive icon */}
{primaryPreviewUrl && !isArchive ? (
) : (
)}
{/* File metadata */}
{primaryFile?.name || 'Primary file'}
{isArchive
? `Archive · ${screenshots.length} screenshot${screenshots.length !== 1 ? 's' : ''}`
: fileMetadata.resolution !== '—'
? `${fileMetadata.resolution} · ${fileMetadata.size}`
: fileMetadata.size}
{isArchive ? 'Archive' : 'Image'}
Content type
Choose the main content family first.
Step 2a
{contentTypeOptions.length === 0 && (
No content types are available right now.
)}
{selectedContentType && !isContentTypeChooserOpen && (
Selected content type
{selectedContentType.name}
{filteredCategoryTree.length > 0
? `Continue by choosing one of the ${filteredCategoryTree.length} matching categories below.`
: 'This content type does not have categories yet.'}
setIsContentTypeChooserOpen(true)}
className="inline-flex items-center justify-center rounded-xl border border-white/10 bg-white/[0.05] px-3 py-2 text-sm font-medium text-slate-200 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white"
>
Change
)}
{(!selectedContentType || isContentTypeChooserOpen) && (
{contentTypeOptions.map((ct) => {
const typeValue = String(getContentTypeValue(ct))
const isActive = typeValue === String(metadata.contentType || '')
const visualKey = getContentTypeVisualKey(ct)
const categoryCount = Array.isArray(ct.categories) ? ct.categories.length : 0
return (
{
setIsContentTypeChooserOpen(false)
setIsCategoryChooserOpen(true)
onContentTypeChange(typeValue)
}}
className={[
'group flex w-full items-center gap-3 rounded-2xl border px-3 py-3 text-left transition-all',
isActive
? 'border-emerald-400/40 bg-emerald-400/10 shadow-[0_0_0_1px_rgba(52,211,153,0.18)]'
: 'border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.06]',
].join(' ')}
aria-pressed={isActive}
>
{ e.currentTarget.style.display = 'none' }}
/>
{ct.name}
{categoryCount} {categoryCount === 1 ? 'category' : 'categories'}
{isActive ? 'Selected' : 'Open'}
)
})}
)}
{metadataErrors.contentType && {metadataErrors.contentType}
}
Category path
Choose the main branch first, then refine with a subcategory when needed.
Step 2b
{!selectedContentType && (
Select a content type first
Once you choose the content type, the matching category tree will appear here.
)}
{selectedContentType && (
{selectedContentType.name}
contains {filteredCategoryTree.length} top-level {filteredCategoryTree.length === 1 ? 'category' : 'categories'}
{selectedRoot && !isCategoryChooserOpen && (
Selected category
{selectedRoot.name}
{subCategories.length > 0
? `Next step: choose one of the ${subCategories.length} subcategories below.`
: 'This category is complete. No subcategory is required.'}
{ setCategorySearch(''); setIsCategoryChooserOpen(true) }}
className="inline-flex items-center justify-center rounded-xl border border-white/10 bg-white/[0.05] px-3 py-2 text-sm font-medium text-slate-200 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white"
>
Change
)}
{(!selectedRoot || isCategoryChooserOpen) && (
{sortedFilteredCategories.length === 0 && (
No categories match “{categorySearch}”
)}
{sortedFilteredCategories.map((cat) => {
const isActive = String(metadata.rootCategoryId || '') === String(cat.id)
const childCount = cat.children?.length || 0
return (
{
setIsCategoryChooserOpen(false)
onRootCategoryChange(String(cat.id))
}}
className={[
'rounded-2xl border px-4 py-4 text-left transition-all',
isActive
? 'border-purple-400/40 bg-purple-400/12 shadow-[0_0_0_1px_rgba(192,132,252,0.15)]'
: 'border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]',
].join(' ')}
>
{cat.name}
{childCount > 0 ? `${childCount} subcategories available` : 'Standalone category'}
{isActive ? 'Selected' : 'Choose'}
)
})}
)}
{selectedRoot && subCategories.length > 0 && (
Subcategories
Refine {selectedRoot.name} with one more level.
{subCategories.length}
{!metadata.subCategoryId && requiresSubCategory && (
Subcategory still needs to be selected.
)}
{selectedSubCategory && !isSubCategoryChooserOpen && (
Selected subcategory
{selectedSubCategory.name}
Final category path: {selectedRoot.name} / {selectedSubCategory.name}
{ setSubCategorySearch(''); setIsSubCategoryChooserOpen(true) }}
className="inline-flex items-center justify-center rounded-xl border border-white/10 bg-white/[0.05] px-3 py-2 text-sm font-medium text-slate-200 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white"
>
Change
)}
{(!selectedSubCategory || isSubCategoryChooserOpen) && (
{sortedFilteredSubCategories.length === 0 && (
No subcategories match “{subCategorySearch}”
)}
{sortedFilteredSubCategories.map((sub) => {
const isActive = String(metadata.subCategoryId || '') === String(sub.id)
return (
{
setIsSubCategoryChooserOpen(false)
onSubCategoryChange(String(sub.id))
}}
className={[
'group rounded-2xl border px-4 py-3 text-left transition-all',
isActive
? 'border-cyan-400/40 bg-cyan-400/[0.13] shadow-[0_0_0_1px_rgba(34,211,238,0.14)]'
: 'border-white/10 bg-white/[0.04] hover:border-white/20 hover:bg-white/[0.06]',
].join(' ')}
>
{sub.name}
Subcategory option
{isActive ? 'Selected' : 'Choose'}
)
})}
)}
)}
{selectedRoot && subCategories.length === 0 && (
{selectedRoot.name} does not have subcategories. Selecting it is enough.
)}
)}
{metadataErrors.category && {metadataErrors.category}
}
{/* Title, tags, description, rights */}
)
}