Save workspace changes
This commit is contained in:
@@ -187,6 +187,10 @@ function operatorOptionsForField(field) {
|
||||
return [{ value: 'equals', label: 'Is' }]
|
||||
}
|
||||
|
||||
if (field === 'tags') {
|
||||
return [{ value: 'equals', label: 'Has tag' }]
|
||||
}
|
||||
|
||||
return [
|
||||
{ value: 'contains', label: 'Contains' },
|
||||
{ value: 'equals', label: 'Equals' },
|
||||
@@ -304,6 +308,11 @@ function buildRuleSummary(rule, smartRuleOptions) {
|
||||
return rule.value ? 'Mature artworks only' : 'Artworks not marked mature'
|
||||
}
|
||||
|
||||
if (rule.field === 'tags') {
|
||||
const tag = String(rule.value || '').trim()
|
||||
return tag ? `Has tag: ${tag}` : 'Tag — no value set'
|
||||
}
|
||||
|
||||
const label = humanizeField(rule.field, smartRuleOptions)
|
||||
const value = String(rule.value || '').trim() || 'Any value'
|
||||
return `${label} ${rule.operator} ${value}`
|
||||
@@ -581,6 +590,33 @@ function StudioTabButton({ active, label, icon, onClick }) {
|
||||
)
|
||||
}
|
||||
|
||||
function AdvancedSection({ title, icon, children, defaultOpen = false }) {
|
||||
const [open, setOpen] = useState(defaultOpen)
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-[24px] border border-white/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
className="flex w-full items-center justify-between px-5 py-4 text-left transition hover:bg-white/[0.03]"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-xl border border-white/10 bg-white/[0.04]">
|
||||
<i className={`fa-solid ${icon} text-[11px] text-slate-400`} />
|
||||
</span>
|
||||
<span className="text-sm font-semibold tracking-[-0.01em] text-white">{title}</span>
|
||||
</div>
|
||||
<i className={`fa-solid ${open ? 'fa-chevron-up' : 'fa-chevron-down'} text-[10px] text-slate-500 transition-transform`} />
|
||||
</button>
|
||||
{open ? (
|
||||
<div className="space-y-5 border-t border-white/10 px-5 pb-6 pt-5">
|
||||
{children}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SmartRuleRow({
|
||||
rule,
|
||||
index,
|
||||
@@ -670,6 +706,16 @@ function SmartRuleRow({
|
||||
)}
|
||||
</select>
|
||||
</Field>
|
||||
) : rule.field === 'tags' ? (
|
||||
<Field label="Value" help="Type a tag name exactly as it appears on your artworks.">
|
||||
<input
|
||||
type="text"
|
||||
value={rule.value}
|
||||
onChange={(event) => onValueChange(event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="e.g. dark-fantasy"
|
||||
/>
|
||||
</Field>
|
||||
) : valueOptions.length ? (
|
||||
<Field label="Value">
|
||||
<select
|
||||
@@ -1803,10 +1849,12 @@ export default function CollectionManage() {
|
||||
<div>
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Collection Studio</p>
|
||||
<h1 className="mt-2 text-3xl font-semibold tracking-[-0.04em] text-white md:text-4xl">
|
||||
{mode === 'create' ? 'Create a v4 collection' : collectionState?.title || 'Manage collection'}
|
||||
{mode === 'create' ? 'New Collection' : collectionState?.title || 'Manage Collection'}
|
||||
</h1>
|
||||
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-slate-300">
|
||||
Collections now carry lifecycle, presentation, campaign, and series metadata alongside the artwork curation itself. Use manual mode for exact storytelling or smart rules for creator-first automation.
|
||||
{mode === 'create'
|
||||
? 'Give your collection a title and choose who can see it — that is all you need to get started. Expand the sections below to add descriptions, presentation options, scheduling, and more.'
|
||||
: 'Manage your collection details, artworks, members, and settings from the tabs below.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -1843,19 +1891,20 @@ export default function CollectionManage() {
|
||||
<ModeButton
|
||||
active={!isSmartMode}
|
||||
title="Manual"
|
||||
description="Curate artworks yourself, control the exact order, and choose a specific cover from attached pieces."
|
||||
description="You choose every artwork. Control the exact order, set a cover, and tell the story your way."
|
||||
icon="fa-hand-sparkles"
|
||||
onClick={() => updateForm('mode', 'manual')}
|
||||
/>
|
||||
<ModeButton
|
||||
active={isSmartMode}
|
||||
title="Smart"
|
||||
description="Build a rule-based collection that automatically pulls matching artworks from your own published gallery."
|
||||
description="Define rules and let the collection fill automatically from your published artworks. Great for keeping series fresh."
|
||||
icon="fa-wand-magic-sparkles"
|
||||
onClick={() => updateForm('mode', 'smart')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* === Essential fields — always visible === */}
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Title">
|
||||
<input
|
||||
@@ -1867,109 +1916,285 @@ export default function CollectionManage() {
|
||||
maxLength={120}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Slug" help="Used in the collection URL.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.slug}
|
||||
onChange={(event) => {
|
||||
setSlugTouched(true)
|
||||
updateForm('slug', slugify(event.target.value))
|
||||
}}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="dark-fantasy-series"
|
||||
maxLength={140}
|
||||
/>
|
||||
<Field label="Visibility">
|
||||
<select value={form.visibility} onChange={(event) => updateForm('visibility', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="public">Public — visible to everyone</option>
|
||||
<option value="unlisted">Unlisted — accessible by link only</option>
|
||||
<option value="private">Private — only you can see it</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Subtitle" help="Optional short line that sits under the title.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.subtitle}
|
||||
onChange={(event) => updateForm('subtitle', event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="A moody archive of midnight environments"
|
||||
maxLength={160}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Summary" help="Optional short summary for cards and meta previews.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.summary}
|
||||
onChange={(event) => updateForm('summary', event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="Best performing sci-fi wallpapers from the last year"
|
||||
maxLength={320}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field label="Description">
|
||||
<textarea
|
||||
value={form.description}
|
||||
onChange={(event) => updateForm('description', event.target.value)}
|
||||
className="min-h-[128px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="Describe the mood, focus, or story behind this showcase."
|
||||
maxLength={1000}
|
||||
<Field label="URL Slug" help="Auto-generated from the title. Edit to customise the collection URL.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.slug}
|
||||
onChange={(event) => {
|
||||
setSlugTouched(true)
|
||||
updateForm('slug', slugify(event.target.value))
|
||||
}}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="dark-fantasy-series"
|
||||
maxLength={140}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Collection Type">
|
||||
<select value={form.type} onChange={(event) => updateForm('type', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="personal">Personal</option>
|
||||
<option value="community">Community</option>
|
||||
<option value="editorial">Editorial</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Collaboration">
|
||||
<select value={form.collaboration_mode} onChange={(event) => updateForm('collaboration_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="closed">Closed</option>
|
||||
<option value="invite_only">Invite Only</option>
|
||||
<option value="open">Open Submissions</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Event Key" help="Internal campaign identifier for discovery and promotion logic.">
|
||||
<input type="text" value={form.event_key} onChange={(event) => updateForm('event_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Event Label" help="Optional campaign or seasonal label.">
|
||||
<input type="text" value={form.event_label} onChange={(event) => updateForm('event_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
</div>
|
||||
<Field label="Summary" help="A short line shown on collection cards and in search results.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.summary}
|
||||
onChange={(event) => updateForm('summary', event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="Best performing sci-fi wallpapers from the last year"
|
||||
maxLength={320}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Season Key" help="Optional seasonal key used for grouped landing surfaces.">
|
||||
<input type="text" value={form.season_key} onChange={(event) => updateForm('season_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Badge Label" help="Short public badge for cards and headers.">
|
||||
<input type="text" value={form.badge_label} onChange={(event) => updateForm('badge_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Spotlight Style" help="Choose how the public campaign banner should be framed.">
|
||||
<select value={form.spotlight_style} onChange={(event) => updateForm('spotlight_style', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="default">Default</option>
|
||||
<option value="editorial">Editorial</option>
|
||||
<option value="seasonal">Seasonal</option>
|
||||
<option value="challenge">Challenge</option>
|
||||
<option value="community">Community</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Banner Text" help="Optional short line displayed as the collection spotlight banner.">
|
||||
<input type="text" value={form.banner_text} onChange={(event) => updateForm('banner_text', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={200} />
|
||||
</Field>
|
||||
</div>
|
||||
{/* === Advanced sections — collapsed on create, expanded on edit === */}
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Publish At" help="Leave empty to publish immediately. Future times keep the collection off public surfaces until it goes live.">
|
||||
<input type="datetime-local" value={form.published_at} onChange={(event) => updateForm('published_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
<Field label="Unpublish At" help="Optional automatic sunset time for seasonal or editorial collections.">
|
||||
<input type="datetime-local" value={form.unpublished_at} onChange={(event) => updateForm('unpublished_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
</div>
|
||||
<AdvancedSection title="Description & Presentation" icon="fa-palette" defaultOpen={mode === 'edit'}>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Subtitle" help="Optional short line that sits under the title.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.subtitle}
|
||||
onChange={(event) => updateForm('subtitle', event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="A moody archive of midnight environments"
|
||||
maxLength={160}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Presentation Style">
|
||||
<select value={form.presentation_style} onChange={(event) => updateForm('presentation_style', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="standard">Standard</option>
|
||||
<option value="editorial_grid">Editorial Grid</option>
|
||||
<option value="hero_grid">Hero Grid</option>
|
||||
<option value="masonry">Masonry</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Lifecycle State" help="Controls whether the collection should be treated as draft, scheduled, published, or retired.">
|
||||
<Field label="Description" help="Describe the mood, focus, or story behind this showcase.">
|
||||
<textarea
|
||||
value={form.description}
|
||||
onChange={(event) => updateForm('description', event.target.value)}
|
||||
className="min-h-[128px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="A curated selection of pieces that share a common visual language…"
|
||||
maxLength={1000}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Emphasis Mode">
|
||||
<select value={form.emphasis_mode} onChange={(event) => updateForm('emphasis_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="cover_heavy">Cover Heavy</option>
|
||||
<option value="balanced">Balanced</option>
|
||||
<option value="artwork_first">Artwork First</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Theme">
|
||||
<select value={form.theme_token} onChange={(event) => updateForm('theme_token', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="default">Default</option>
|
||||
<option value="subtle-blue">Subtle Blue</option>
|
||||
<option value="violet">Violet</option>
|
||||
<option value="amber">Amber</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
{!isSmartMode ? (
|
||||
<Field label="Sort Order" help="Manual keeps the display order under your direct control.">
|
||||
<select value={form.sort_mode} onChange={(event) => updateForm('sort_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="manual">Manual</option>
|
||||
<option value="newest">Newest first</option>
|
||||
<option value="oldest">Oldest first</option>
|
||||
<option value="popular">Most popular</option>
|
||||
</select>
|
||||
</Field>
|
||||
) : (
|
||||
<Field label="Match Mode" help="All rules must match, or any one rule is enough.">
|
||||
<select
|
||||
value={smartRules.match}
|
||||
onChange={(event) => setSmartRules((current) => ({ ...current, match: event.target.value }))}
|
||||
className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35"
|
||||
>
|
||||
<option value="all">All rules</option>
|
||||
<option value="any">Any rule</option>
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{!isSmartMode ? (
|
||||
<Field label="Cover Artwork" help={attachedCoverOptions.length ? 'Choose a cover from artworks already attached to this collection.' : 'Attach artworks first to pick a manual cover.'}>
|
||||
<select
|
||||
value={form.cover_artwork_id}
|
||||
onChange={(event) => updateForm('cover_artwork_id', event.target.value)}
|
||||
disabled={!attachedCoverOptions.length}
|
||||
className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 disabled:cursor-not-allowed disabled:text-slate-500"
|
||||
>
|
||||
<option value="">Automatic cover</option>
|
||||
{attachedCoverOptions.map((artwork) => (
|
||||
<option key={artwork.id} value={artwork.id}>{artwork.title}</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
) : (
|
||||
<Field label="Smart Sort" help="How matching artworks should be ordered in this collection.">
|
||||
<select
|
||||
value={smartRules.sort}
|
||||
onChange={(event) => {
|
||||
setSmartRules((current) => ({ ...current, sort: event.target.value }))
|
||||
updateForm('sort_mode', event.target.value)
|
||||
}}
|
||||
className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35"
|
||||
>
|
||||
{(smartRuleOptions?.sort_options || []).map((option) => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
</div>
|
||||
</AdvancedSection>
|
||||
|
||||
<AdvancedSection title="Collaboration & Access" icon="fa-user-group" defaultOpen={mode === 'edit'}>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Collection Type">
|
||||
<select value={form.type} onChange={(event) => updateForm('type', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="personal">Personal</option>
|
||||
<option value="community">Community</option>
|
||||
<option value="editorial">Editorial</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Collaboration Mode">
|
||||
<select value={form.collaboration_mode} onChange={(event) => updateForm('collaboration_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="closed">Closed — curated by you only</option>
|
||||
<option value="invite_only">Invite only</option>
|
||||
<option value="open">Open submissions</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.allow_submissions} onChange={(event) => updateForm('allow_submissions', event.target.checked)} />
|
||||
Allow submissions
|
||||
</label>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.allow_comments} onChange={(event) => updateForm('allow_comments', event.target.checked)} />
|
||||
Allow comments
|
||||
</label>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.allow_saves} onChange={(event) => updateForm('allow_saves', event.target.checked)} />
|
||||
Allow saves
|
||||
</label>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.commercial_eligibility} onChange={(event) => updateForm('commercial_eligibility', event.target.checked)} />
|
||||
Commercially eligible
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{form.type === 'editorial' ? (
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
<Field label="Editorial Owner" help="Choose whether this editorial lives under the current curator, another staff account, or the system identity.">
|
||||
<select value={form.editorial_owner_mode} onChange={(event) => updateForm('editorial_owner_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="creator">Current curator</option>
|
||||
<option value="staff_account">Staff account</option>
|
||||
<option value="system">System editorial identity</option>
|
||||
</select>
|
||||
</Field>
|
||||
{form.editorial_owner_mode === 'staff_account' ? (
|
||||
<Field label="Staff Account Username" help="Must be an admin or moderator username.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.editorial_owner_username}
|
||||
onChange={(event) => updateForm('editorial_owner_username', event.target.value.trimStart())}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="skinbase-editorial"
|
||||
maxLength={60}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
{form.editorial_owner_mode === 'system' ? (
|
||||
<Field label="System Owner Label" help="Public-facing label for system-owned editorials.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.editorial_owner_label}
|
||||
onChange={(event) => updateForm('editorial_owner_label', event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="Skinbase Editorial"
|
||||
maxLength={120}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</AdvancedSection>
|
||||
|
||||
<AdvancedSection title="Campaign & Events" icon="fa-bullhorn" defaultOpen={mode === 'edit'}>
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Event Key" help="Internal identifier used by discovery and promotion logic.">
|
||||
<input type="text" value={form.event_key} onChange={(event) => updateForm('event_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Event Label">
|
||||
<input type="text" value={form.event_label} onChange={(event) => updateForm('event_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
<Field label="Season Key" help="Groups related collections by season on landing surfaces.">
|
||||
<input type="text" value={form.season_key} onChange={(event) => updateForm('season_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Badge Label" help="Short public badge on cards and headers.">
|
||||
<input type="text" value={form.badge_label} onChange={(event) => updateForm('badge_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Campaign Key" help="Operational identifier for recommendation and placement logic.">
|
||||
<input type="text" value={form.campaign_key} onChange={(event) => updateForm('campaign_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Campaign Label" help="Public-facing campaign or promotion label.">
|
||||
<input type="text" value={form.campaign_label} onChange={(event) => updateForm('campaign_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
<Field label="Spotlight Style" help="Controls the visual frame for the public campaign banner.">
|
||||
<select value={form.spotlight_style} onChange={(event) => updateForm('spotlight_style', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="default">Default</option>
|
||||
<option value="editorial">Editorial</option>
|
||||
<option value="seasonal">Seasonal</option>
|
||||
<option value="challenge">Challenge</option>
|
||||
<option value="community">Community</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Banner Text" help="Short line shown in the collection spotlight banner.">
|
||||
<input type="text" value={form.banner_text} onChange={(event) => updateForm('banner_text', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={200} />
|
||||
</Field>
|
||||
</div>
|
||||
</AdvancedSection>
|
||||
|
||||
<AdvancedSection title="Series" icon="fa-layer-group" defaultOpen={mode === 'edit'}>
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
<Field label="Series Key" help="Use the same key across all linked collections in a series.">
|
||||
<input type="text" value={form.series_key} onChange={(event) => updateForm('series_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Series Title" help="Optional public heading shown for the whole series.">
|
||||
<input type="text" value={form.series_title} onChange={(event) => updateForm('series_title', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={160} />
|
||||
</Field>
|
||||
<Field label="Series Order" help="Sequence position for public next & previous navigation.">
|
||||
<input type="number" min="1" max="9999" value={form.series_order} onChange={(event) => updateForm('series_order', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field label="Series Description" help="Optional public intro shown on series landing pages.">
|
||||
<textarea
|
||||
value={form.series_description}
|
||||
onChange={(event) => updateForm('series_description', event.target.value)}
|
||||
className="min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
maxLength={400}
|
||||
/>
|
||||
</Field>
|
||||
</AdvancedSection>
|
||||
|
||||
<AdvancedSection title="Scheduling & Lifecycle" icon="fa-calendar-days" defaultOpen={mode === 'edit'}>
|
||||
<Field label="Lifecycle State" help="Draft keeps it hidden. Published makes it live. Archived retires it from active surfaces.">
|
||||
<select value={form.lifecycle_state} onChange={(event) => updateForm('lifecycle_state', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="draft">Draft</option>
|
||||
<option value="scheduled">Scheduled</option>
|
||||
@@ -1979,226 +2204,72 @@ export default function CollectionManage() {
|
||||
<option value="expired">Expired</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Presentation Style">
|
||||
<select value={form.presentation_style} onChange={(event) => updateForm('presentation_style', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="standard">Standard</option>
|
||||
<option value="editorial_grid">Editorial Grid</option>
|
||||
<option value="hero_grid">Hero Grid</option>
|
||||
<option value="masonry">Masonry</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Emphasis Mode">
|
||||
<select value={form.emphasis_mode} onChange={(event) => updateForm('emphasis_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="cover_heavy">Cover Heavy</option>
|
||||
<option value="balanced">Balanced</option>
|
||||
<option value="artwork_first">Artwork First</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Theme Token">
|
||||
<select value={form.theme_token} onChange={(event) => updateForm('theme_token', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="default">Default</option>
|
||||
<option value="subtle-blue">Subtle Blue</option>
|
||||
<option value="violet">Violet</option>
|
||||
<option value="amber">Amber</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Series Key" help="Use the same key across linked collections in a series.">
|
||||
<input type="text" value={form.series_key} onChange={(event) => updateForm('series_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Series Title" help="Optional public heading for the whole series.">
|
||||
<input type="text" value={form.series_title} onChange={(event) => updateForm('series_title', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={160} />
|
||||
</Field>
|
||||
<Field label="Series Order" help="Sequence within the series for public next and previous navigation.">
|
||||
<input type="number" min="1" max="9999" value={form.series_order} onChange={(event) => updateForm('series_order', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
<Field label="Campaign Key" help="Operational campaign identifier for discovery, placements, and recommendations.">
|
||||
<input type="text" value={form.campaign_key} onChange={(event) => updateForm('campaign_key', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={80} />
|
||||
</Field>
|
||||
<Field label="Campaign Label" help="Public-facing campaign or promotion label.">
|
||||
<input type="text" value={form.campaign_label} onChange={(event) => updateForm('campaign_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
</div>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Publish At" help="Leave empty to publish immediately. A future time keeps it off public surfaces until it goes live.">
|
||||
<input type="datetime-local" value={form.published_at} onChange={(event) => updateForm('published_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
<Field label="Unpublish At" help="Optional automatic sunset time for seasonal or editorial collections.">
|
||||
<input type="datetime-local" value={form.unpublished_at} onChange={(event) => updateForm('unpublished_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field label="Series Description" help="Optional public intro shown on series landing pages and collection series callouts.">
|
||||
<textarea
|
||||
value={form.series_description}
|
||||
onChange={(event) => updateForm('series_description', event.target.value)}
|
||||
className="min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
maxLength={400}
|
||||
/>
|
||||
</Field>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Archive At" help="Optional timestamp for moving the collection to long-term archive workflows.">
|
||||
<input type="datetime-local" value={form.archived_at} onChange={(event) => updateForm('archived_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
<Field label="Expire At" help="Optional hard expiry for promotional or seasonal collections.">
|
||||
<input type="datetime-local" value={form.expired_at} onChange={(event) => updateForm('expired_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
</div>
|
||||
</AdvancedSection>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Archive At" help="Optional timestamp for moving the collection into long-term archive workflows.">
|
||||
<input type="datetime-local" value={form.archived_at} onChange={(event) => updateForm('archived_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
<Field label="Expire At" help="Optional hard expiry for promotional or seasonal collections.">
|
||||
<input type="datetime-local" value={form.expired_at} onChange={(event) => updateForm('expired_at', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" />
|
||||
</Field>
|
||||
<Field label="Promotion Tier">
|
||||
<input type="text" value={form.promotion_tier} onChange={(event) => updateForm('promotion_tier', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={40} />
|
||||
</Field>
|
||||
<Field label="Monetization Status">
|
||||
<input type="text" value={form.monetization_ready_status} onChange={(event) => updateForm('monetization_ready_status', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={40} />
|
||||
</Field>
|
||||
</div>
|
||||
<AdvancedSection title="Commercial & Administration" icon="fa-briefcase" defaultOpen={mode === 'edit'}>
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Promotion Tier">
|
||||
<input type="text" value={form.promotion_tier} onChange={(event) => updateForm('promotion_tier', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={40} />
|
||||
</Field>
|
||||
<Field label="Monetization Status">
|
||||
<input type="text" value={form.monetization_ready_status} onChange={(event) => updateForm('monetization_ready_status', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={40} />
|
||||
</Field>
|
||||
<Field label="Sponsorship Label">
|
||||
<input type="text" value={form.sponsorship_label} onChange={(event) => updateForm('sponsorship_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
<Field label="Partner Label">
|
||||
<input type="text" value={form.partner_label} onChange={(event) => updateForm('partner_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Field label="Sponsorship Label">
|
||||
<input type="text" value={form.sponsorship_label} onChange={(event) => updateForm('sponsorship_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
<Field label="Partner Label">
|
||||
<input type="text" value={form.partner_label} onChange={(event) => updateForm('partner_label', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={120} />
|
||||
</Field>
|
||||
<Field label="Brand Safe Status">
|
||||
<input type="text" value={form.brand_safe_status} onChange={(event) => updateForm('brand_safe_status', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={40} />
|
||||
</Field>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.analytics_enabled} onChange={(event) => updateForm('analytics_enabled', event.target.checked)} />
|
||||
Analytics enabled
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<Field label="Brand Safe Status">
|
||||
<input type="text" value={form.brand_safe_status} onChange={(event) => updateForm('brand_safe_status', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]" maxLength={40} />
|
||||
</Field>
|
||||
<label className="flex items-center gap-3 self-end rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.analytics_enabled} onChange={(event) => updateForm('analytics_enabled', event.target.checked)} />
|
||||
Analytics enabled
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Field label="Editorial Notes" help="Internal editorial context for campaign planning, curation rationale, and staff handoff.">
|
||||
<textarea
|
||||
value={form.editorial_notes}
|
||||
onChange={(event) => updateForm('editorial_notes', event.target.value)}
|
||||
className="min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
maxLength={2000}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{canModerate ? (
|
||||
<Field label="Staff Commercial Notes" help="Internal admin-only notes for sponsorship readiness, partner handling, and commercial review.">
|
||||
<Field label="Editorial Notes" help="Internal editorial context for campaign planning, curation rationale, and staff handoff.">
|
||||
<textarea
|
||||
value={form.staff_commercial_notes}
|
||||
onChange={(event) => updateForm('staff_commercial_notes', event.target.value)}
|
||||
className="min-h-[96px] w-full rounded-2xl border border-amber-300/15 bg-amber-400/10 px-4 py-3 text-white outline-none transition focus:border-amber-300/35 focus:bg-amber-400/15"
|
||||
value={form.editorial_notes}
|
||||
onChange={(event) => updateForm('editorial_notes', event.target.value)}
|
||||
className="min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
maxLength={2000}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
{form.type === 'editorial' ? (
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
<Field label="Editorial Owner Mode" help="Choose whether this editorial lives under the current curator, another staff account, or the system editorial identity.">
|
||||
<select value={form.editorial_owner_mode} onChange={(event) => updateForm('editorial_owner_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="creator">Current curator</option>
|
||||
<option value="staff_account">Staff account</option>
|
||||
<option value="system">System editorial identity</option>
|
||||
</select>
|
||||
{canModerate ? (
|
||||
<Field label="Staff Commercial Notes" help="Admin-only notes for sponsorship readiness, partner handling, and commercial review.">
|
||||
<textarea
|
||||
value={form.staff_commercial_notes}
|
||||
onChange={(event) => updateForm('staff_commercial_notes', event.target.value)}
|
||||
className="min-h-[96px] w-full rounded-2xl border border-amber-300/15 bg-amber-400/10 px-4 py-3 text-white outline-none transition focus:border-amber-300/35 focus:bg-amber-400/15"
|
||||
maxLength={2000}
|
||||
/>
|
||||
</Field>
|
||||
{form.editorial_owner_mode === 'staff_account' ? (
|
||||
<Field label="Staff Account Username" help="Must be an admin or moderator username.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.editorial_owner_username}
|
||||
onChange={(event) => updateForm('editorial_owner_username', event.target.value.trimStart())}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="skinbase-editorial"
|
||||
maxLength={60}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
{form.editorial_owner_mode === 'system' ? (
|
||||
<Field label="System Owner Label" help="Public-facing label for system-owned editorials.">
|
||||
<input
|
||||
type="text"
|
||||
value={form.editorial_owner_label}
|
||||
onChange={(event) => updateForm('editorial_owner_label', event.target.value)}
|
||||
className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 focus:bg-white/[0.06]"
|
||||
placeholder="Skinbase Editorial"
|
||||
maxLength={120}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.allow_submissions} onChange={(event) => updateForm('allow_submissions', event.target.checked)} />
|
||||
Allow submissions
|
||||
</label>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.allow_comments} onChange={(event) => updateForm('allow_comments', event.target.checked)} />
|
||||
Allow comments
|
||||
</label>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.allow_saves} onChange={(event) => updateForm('allow_saves', event.target.checked)} />
|
||||
Allow saves
|
||||
</label>
|
||||
<label className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
|
||||
<input type="checkbox" checked={form.commercial_eligibility} onChange={(event) => updateForm('commercial_eligibility', event.target.checked)} />
|
||||
Commercially eligible
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
<Field label="Visibility">
|
||||
<select value={form.visibility} onChange={(event) => updateForm('visibility', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="public">Public</option>
|
||||
<option value="unlisted">Unlisted</option>
|
||||
<option value="private">Private</option>
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
{!isSmartMode ? (
|
||||
<Field label="Sort Mode" help="Manual keeps the display order under your control.">
|
||||
<select value={form.sort_mode} onChange={(event) => updateForm('sort_mode', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
|
||||
<option value="manual">Manual</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="oldest">Oldest</option>
|
||||
<option value="popular">Popular</option>
|
||||
</select>
|
||||
</Field>
|
||||
) : (
|
||||
<Field label="Match Mode" help="All means every rule must match. Any is broader.">
|
||||
<select
|
||||
value={smartRules.match}
|
||||
onChange={(event) => setSmartRules((current) => ({ ...current, match: event.target.value }))}
|
||||
className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35"
|
||||
>
|
||||
<option value="all">All rules</option>
|
||||
<option value="any">Any rule</option>
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{!isSmartMode ? (
|
||||
<Field label="Cover Artwork" help={attachedCoverOptions.length ? 'Choose a cover from artworks already attached to this collection.' : 'Attach artworks first to pick a manual cover.'}>
|
||||
<select
|
||||
value={form.cover_artwork_id}
|
||||
onChange={(event) => updateForm('cover_artwork_id', event.target.value)}
|
||||
disabled={!attachedCoverOptions.length}
|
||||
className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35 disabled:cursor-not-allowed disabled:text-slate-500"
|
||||
>
|
||||
<option value="">Automatic cover</option>
|
||||
{attachedCoverOptions.map((artwork) => (
|
||||
<option key={artwork.id} value={artwork.id}>{artwork.title}</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
) : (
|
||||
<Field label="Smart Sort" help="How matching artworks should be ordered.">
|
||||
<select
|
||||
value={smartRules.sort}
|
||||
onChange={(event) => {
|
||||
setSmartRules((current) => ({ ...current, sort: event.target.value }))
|
||||
updateForm('sort_mode', event.target.value)
|
||||
}}
|
||||
className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35"
|
||||
>
|
||||
{(smartRuleOptions?.sort_options || []).map((option) => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</AdvancedSection>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<button
|
||||
@@ -2337,13 +2408,13 @@ export default function CollectionManage() {
|
||||
) : null}
|
||||
|
||||
<section className="rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">v4 Guidance</p>
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Quick Start</p>
|
||||
<div className="mt-4 space-y-3 text-sm leading-relaxed text-slate-300">
|
||||
<div className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3">
|
||||
Manual collections work best for hand-picked sequences, premium presentation modes, and campaign landing pages.
|
||||
Start with just a title — everything else can be filled in later. The advanced sections are there when you need them.
|
||||
</div>
|
||||
<div className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3">
|
||||
Smart collections are ideal for creator-first rulesets that keep series or editorial shelves fresh without cross-user leakage.
|
||||
<span className="font-semibold text-white">Manual</span> is great for hand-picked storytelling. <span className="font-semibold text-white">Smart</span> keeps a collection up to date automatically using rules you define.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user