Remove dead admin UI code, redesign dashboard followers/following and upload experiences, and add schema audit tooling with repair migrations for forum and upload drift.
164 lines
6.3 KiB
JavaScript
164 lines
6.3 KiB
JavaScript
import React from 'react'
|
|
import ContentTypeSelector from '../ContentTypeSelector'
|
|
import CategorySelector from '../CategorySelector'
|
|
import UploadSidebar from '../UploadSidebar'
|
|
|
|
/**
|
|
* 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,
|
|
onToggleRights,
|
|
}) {
|
|
return (
|
|
<div className="space-y-5 rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-6 shadow-[0_24px_90px_rgba(2,8,23,0.28)] backdrop-blur sm:p-7">
|
|
{/* Step header */}
|
|
<div className="rounded-2xl border border-white/8 bg-white/[0.04] px-5 py-4">
|
|
<h2
|
|
ref={headingRef}
|
|
tabIndex={-1}
|
|
className="text-lg font-semibold text-white focus:outline-none"
|
|
>
|
|
Artwork details
|
|
</h2>
|
|
<p className="mt-1 text-sm text-white/60">
|
|
Complete required metadata and rights confirmation before publishing.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Uploaded asset summary */}
|
|
<div className="rounded-2xl ring-1 ring-white/8 bg-white/[0.025] p-5">
|
|
<p className="mb-3 text-[11px] uppercase tracking-wide text-white/45">Uploaded asset</p>
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
{/* Thumbnail / Archive icon */}
|
|
{primaryPreviewUrl && !isArchive ? (
|
|
<div className="flex h-[120px] w-[120px] items-center justify-center overflow-hidden rounded-xl ring-1 ring-white/10 bg-black/30 shrink-0">
|
|
<img
|
|
src={primaryPreviewUrl}
|
|
alt="Uploaded artwork thumbnail"
|
|
className="max-h-full max-w-full object-contain"
|
|
loading="lazy"
|
|
decoding="async"
|
|
width={120}
|
|
height={120}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="grid h-[120px] w-[120px] place-items-center rounded-xl ring-1 ring-white/10 bg-white/5 shrink-0">
|
|
<svg className="h-8 w-8 text-white/30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
|
|
{/* File metadata */}
|
|
<div className="min-w-0 space-y-1">
|
|
<p className="truncate text-sm font-medium text-white">
|
|
{primaryFile?.name || 'Primary file'}
|
|
</p>
|
|
<p className="text-xs text-white/50">
|
|
{isArchive
|
|
? `Archive · ${screenshots.length} screenshot${screenshots.length !== 1 ? 's' : ''}`
|
|
: fileMetadata.resolution !== '—'
|
|
? `${fileMetadata.resolution} · ${fileMetadata.size}`
|
|
: fileMetadata.size}
|
|
</p>
|
|
<span className={`inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] ${isArchive ? 'border-amber-400/40 bg-amber-400/10 text-amber-200' : 'border-sky-400/40 bg-sky-400/10 text-sky-200'}`}>
|
|
{isArchive ? 'Archive' : 'Image'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content type selector */}
|
|
<section className="rounded-2xl ring-1 ring-white/10 bg-gradient-to-br from-white/[0.04] to-white/[0.01] p-5 sm:p-6">
|
|
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-white">Content type</h3>
|
|
<p className="mt-0.5 text-xs text-white/55">Choose what kind of artwork this is.</p>
|
|
</div>
|
|
<span className="rounded-full border border-sky-400/35 bg-sky-400/10 px-2.5 py-0.5 text-[11px] uppercase tracking-widest text-sky-300">
|
|
Step 2a
|
|
</span>
|
|
</div>
|
|
|
|
<ContentTypeSelector
|
|
contentTypes={contentTypes}
|
|
selected={metadata.contentType}
|
|
error={metadataErrors.contentType}
|
|
onChange={onContentTypeChange}
|
|
/>
|
|
</section>
|
|
|
|
{/* Category selector */}
|
|
<section className="rounded-2xl ring-1 ring-white/10 bg-gradient-to-br from-white/[0.04] to-white/[0.01] p-5 sm:p-6">
|
|
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-white">Category</h3>
|
|
<p className="mt-0.5 text-xs text-white/55">
|
|
{requiresSubCategory ? 'Select a category, then a subcategory.' : 'Select a category.'}
|
|
</p>
|
|
</div>
|
|
<span className="rounded-full border border-violet-400/35 bg-violet-400/10 px-2.5 py-0.5 text-[11px] uppercase tracking-widest text-violet-300">
|
|
Step 2b
|
|
</span>
|
|
</div>
|
|
|
|
<CategorySelector
|
|
categories={filteredCategoryTree}
|
|
rootCategoryId={metadata.rootCategoryId}
|
|
subCategoryId={metadata.subCategoryId}
|
|
hasContentType={Boolean(metadata.contentType)}
|
|
error={metadataErrors.category}
|
|
onRootChange={onRootCategoryChange}
|
|
onSubChange={onSubCategoryChange}
|
|
allRoots={allRootCategoryOptions}
|
|
onRootChangeAll={(rootId, contentTypeValue) => {
|
|
if (contentTypeValue) {
|
|
onContentTypeChange(contentTypeValue)
|
|
}
|
|
onRootCategoryChange(rootId)
|
|
}}
|
|
/>
|
|
</section>
|
|
|
|
{/* Title, tags, description, rights */}
|
|
<UploadSidebar
|
|
showHeader={false}
|
|
metadata={metadata}
|
|
suggestedTags={suggestedTags}
|
|
errors={metadataErrors}
|
|
onChangeTitle={onChangeTitle}
|
|
onChangeTags={onChangeTags}
|
|
onChangeDescription={onChangeDescription}
|
|
onToggleRights={onToggleRights}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|