Replace native selects with NovaSelect

This commit is contained in:
2026-05-01 07:45:37 +02:00
parent 67be537c86
commit 35011001ba
55 changed files with 3136 additions and 1662 deletions

View File

@@ -2,6 +2,8 @@ import React, { useEffect, useMemo, useState } from 'react'
import { Head, usePage } from '@inertiajs/react'
import CollectionCard from '../../components/profile/collections/CollectionCard'
import CollectionVisibilityBadge from '../../components/profile/collections/CollectionVisibilityBadge'
import Checkbox from '../../components/ui/Checkbox'
import NovaSelect from '../../components/ui/NovaSelect'
function getCsrfToken() {
if (typeof document === 'undefined') return ''
@@ -466,15 +468,19 @@ function MemberCard({ member, onRoleChange, onRemove, onAccept, onDecline, onTra
</div>
<div className="mt-3 flex flex-wrap gap-2">
{member?.can_revoke ? (
<select
value={member?.role}
onChange={(event) => onRoleChange?.(member, event.target.value)}
className="rounded-xl border border-white/10 bg-[#0d1726] px-3 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white"
>
<option value="editor">Editor</option>
<option value="contributor">Contributor</option>
<option value="viewer">Viewer</option>
</select>
<div className="min-w-[150px]">
<NovaSelect
value={member?.role}
onChange={(value) => onRoleChange?.(member, value)}
options={[
{ value: 'editor', label: 'Editor' },
{ value: 'contributor', label: 'Contributor' },
{ value: 'viewer', label: 'Viewer' },
]}
searchable={false}
className="text-xs font-semibold uppercase tracking-[0.14em]"
/>
</div>
) : null}
{member?.can_accept ? <button type="button" onClick={() => onAccept?.(member)} className="rounded-xl border border-emerald-400/20 bg-emerald-400/10 px-3 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-emerald-100">Accept</button> : null}
{member?.can_decline ? <button type="button" onClick={() => onDecline?.(member)} className="rounded-xl border border-white/12 bg-white/[0.05] px-3 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white">Decline</button> : null}
@@ -528,28 +534,27 @@ function LayoutModuleCard({ module, index, total, onToggle, onSlotChange, onMove
<div className="text-sm font-semibold text-white">{module.label}</div>
<p className="mt-1 text-sm leading-relaxed text-slate-400">{module.description}</p>
</div>
<label className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white">
<input
type="checkbox"
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white">
<Checkbox
checked={module.enabled}
disabled={module.locked}
onChange={(event) => onToggle(module.key, event.target.checked)}
label={module.locked ? 'Required' : (module.enabled ? 'Enabled' : 'Disabled')}
/>
{module.locked ? 'Required' : (module.enabled ? 'Enabled' : 'Disabled')}
</label>
</div>
</div>
<div className="mt-4 grid gap-3 md:grid-cols-[minmax(0,1fr)_auto]">
<Field label="Placement">
<select
<NovaSelect
value={module.slot}
onChange={(event) => onSlotChange(module.key, 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"
>
{module.slots.map((slot) => (
<option key={slot} value={slot}>{slot === 'full' ? 'Full width' : slot === 'main' ? 'Main column' : 'Sidebar'}</option>
))}
</select>
onChange={(value) => onSlotChange(module.key, value)}
options={module.slots.map((slot) => ({
value: slot,
label: slot === 'full' ? 'Full width' : slot === 'main' ? 'Main column' : 'Sidebar',
}))}
searchable={false}
/>
</Field>
<div className="flex flex-wrap items-end gap-2">
@@ -646,27 +651,21 @@ function SmartRuleRow({
<div className="mt-4 grid gap-4 md:grid-cols-[1fr_180px_minmax(0,1.15fr)]">
<Field label="Field">
<select
<NovaSelect
value={rule.field}
onChange={(event) => onFieldChange(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"
>
{fieldOptions.map((option) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
onChange={(value) => onFieldChange(value)}
options={fieldOptions.map((option) => ({ value: option.value, label: option.label }))}
searchable={false}
/>
</Field>
<Field label="Operator">
<select
<NovaSelect
value={rule.operator}
onChange={(event) => onOperatorChange(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"
>
{operatorOptions.map((option) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
onChange={(value) => onOperatorChange(value)}
options={operatorOptions.map((option) => ({ value: option.value, label: option.label }))}
searchable={false}
/>
</Field>
{rule.field === 'created_at' ? (
@@ -688,23 +687,20 @@ function SmartRuleRow({
</Field>
) : rule.field === 'is_featured' || rule.field === 'is_mature' ? (
<Field label="Value">
<select
<NovaSelect
value={rule.value ? 'true' : 'false'}
onChange={(event) => onValueChange(event.target.value === 'true')}
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"
>
{rule.field === 'is_featured' ? (
<>
<option value="true">Featured only</option>
<option value="false">Not featured</option>
</>
) : (
<>
<option value="true">Mature only</option>
<option value="false">Not mature</option>
</>
)}
</select>
onChange={(value) => onValueChange(value === 'true')}
options={rule.field === 'is_featured'
? [
{ value: 'true', label: 'Featured only' },
{ value: 'false', label: 'Not featured' },
]
: [
{ value: 'true', label: 'Mature only' },
{ value: 'false', label: 'Not mature' },
]}
searchable={false}
/>
</Field>
) : rule.field === 'tags' ? (
<Field label="Value" help="Type a tag name exactly as it appears on your artworks.">
@@ -718,16 +714,13 @@ function SmartRuleRow({
</Field>
) : valueOptions.length ? (
<Field label="Value">
<select
<NovaSelect
value={rule.value}
onChange={(event) => onValueChange(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="">Select one</option>
{valueOptions.map((option) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
onChange={(value) => onValueChange(value)}
options={valueOptions.map((option) => ({ value: option.value, label: option.label }))}
placeholder="Select one"
searchable={false}
/>
</Field>
) : (
<Field label="Value">
@@ -1917,11 +1910,7 @@ export default function CollectionManage() {
/>
</Field>
<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>
<NovaSelect value={form.visibility} onChange={(val) => updateForm('visibility', val)} searchable={false} options={[{ value: 'public', label: 'Public — visible to everyone' }, { value: 'unlisted', label: 'Unlisted — accessible by link only' }, { value: 'private', label: 'Private — only you can see it' }]} />
</Field>
</div>
@@ -1965,12 +1954,7 @@ export default function CollectionManage() {
/>
</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>
<NovaSelect value={form.presentation_style} onChange={(val) => updateForm('presentation_style', val)} searchable={false} options={[{ value: 'standard', label: 'Standard' }, { value: 'editorial_grid', label: 'Editorial Grid' }, { value: 'hero_grid', label: 'Hero Grid' }, { value: 'masonry', label: 'Masonry' }]} />
</Field>
</div>
@@ -1986,73 +1970,49 @@ export default function CollectionManage() {
<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>
<NovaSelect value={form.emphasis_mode} onChange={(val) => updateForm('emphasis_mode', val)} searchable={false} options={[{ value: 'cover_heavy', label: 'Cover Heavy' }, { value: 'balanced', label: 'Balanced' }, { value: 'artwork_first', label: 'Artwork First' }]} />
</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>
<NovaSelect value={form.theme_token} onChange={(val) => updateForm('theme_token', val)} searchable={false} options={[{ value: 'default', label: 'Default' }, { value: 'subtle-blue', label: 'Subtle Blue' }, { value: 'violet', label: 'Violet' }, { value: 'amber', label: 'Amber' }]} />
</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>
<NovaSelect value={form.sort_mode} onChange={(val) => updateForm('sort_mode', val)} searchable={false} options={[{ value: 'manual', label: 'Manual' }, { value: 'newest', label: 'Newest first' }, { value: 'oldest', label: 'Oldest first' }, { value: 'popular', label: 'Most popular' }]} />
</Field>
) : (
<Field label="Match Mode" help="All rules must match, or any one rule is enough.">
<select
<NovaSelect
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>
onChange={(val) => setSmartRules((current) => ({ ...current, match: val }))}
searchable={false}
options={[{ value: 'all', label: 'All rules' }, { value: 'any', label: 'Any rule' }]}
/>
</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)}
<NovaSelect
value={String(form.cover_artwork_id || '')}
onChange={(val) => updateForm('cover_artwork_id', val)}
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>
placeholder="Automatic cover"
options={attachedCoverOptions.map((a) => ({ value: String(a.id), label: a.title }))}
/>
</Field>
) : (
<Field label="Smart Sort" help="How matching artworks should be ordered in this collection.">
<select
<NovaSelect
value={smartRules.sort}
onChange={(event) => {
setSmartRules((current) => ({ ...current, sort: event.target.value }))
updateForm('sort_mode', event.target.value)
onChange={(val) => {
setSmartRules((current) => ({ ...current, sort: val }))
updateForm('sort_mode', val)
}}
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>
options={(smartRuleOptions?.sort_options || [])}
/>
</Field>
)}
</div>
@@ -2061,48 +2021,32 @@ export default function CollectionManage() {
<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>
<NovaSelect value={form.type} onChange={(val) => updateForm('type', val)} searchable={false} options={[{ value: 'personal', label: 'Personal' }, { value: 'community', label: 'Community' }, { value: 'editorial', label: 'Editorial' }]} />
</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>
<NovaSelect value={form.collaboration_mode} onChange={(val) => updateForm('collaboration_mode', val)} searchable={false} options={[{ value: 'closed', label: 'Closed — curated by you only' }, { value: 'invite_only', label: 'Invite only' }, { value: 'open', label: 'Open submissions' }]} />
</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 className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<Checkbox checked={form.allow_submissions} onChange={(event) => updateForm('allow_submissions', event.target.checked)} label="Allow submissions" />
</div>
<div className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<Checkbox checked={form.allow_comments} onChange={(event) => updateForm('allow_comments', event.target.checked)} label="Allow comments" />
</div>
<div className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<Checkbox checked={form.allow_saves} onChange={(event) => updateForm('allow_saves', event.target.checked)} label="Allow saves" />
</div>
<div className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<Checkbox checked={form.commercial_eligibility} onChange={(event) => updateForm('commercial_eligibility', event.target.checked)} label="Commercially eligible" />
</div>
</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>
<NovaSelect value={form.editorial_owner_mode} onChange={(val) => updateForm('editorial_owner_mode', val)} searchable={false} options={[{ value: 'creator', label: 'Current curator' }, { value: 'staff_account', label: 'Staff account' }, { value: 'system', label: 'System editorial identity' }]} />
</Field>
{form.editorial_owner_mode === 'staff_account' ? (
<Field label="Staff Account Username" help="Must be an admin or moderator username.">
@@ -2156,13 +2100,7 @@ export default function CollectionManage() {
<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>
<NovaSelect value={form.spotlight_style} onChange={(val) => updateForm('spotlight_style', val)} searchable={false} options={[{ value: 'default', label: 'Default' }, { value: 'editorial', label: 'Editorial' }, { value: 'seasonal', label: 'Seasonal' }, { value: 'challenge', label: 'Challenge' }, { value: 'community', label: 'Community' }]} />
</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} />
@@ -2195,14 +2133,7 @@ export default function CollectionManage() {
<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>
<option value="published">Published</option>
<option value="featured">Featured</option>
<option value="archived">Archived</option>
<option value="expired">Expired</option>
</select>
<NovaSelect value={form.lifecycle_state} onChange={(val) => updateForm('lifecycle_state', val)} searchable={false} options={[{ value: 'draft', label: 'Draft' }, { value: 'scheduled', label: 'Scheduled' }, { value: 'published', label: 'Published' }, { value: 'featured', label: 'Featured' }, { value: 'archived', label: 'Archived' }, { value: 'expired', label: 'Expired' }]} />
</Field>
<div className="grid gap-5 md:grid-cols-2">
@@ -2244,10 +2175,9 @@ export default function CollectionManage() {
<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 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">
<Checkbox checked={form.analytics_enabled} onChange={(event) => updateForm('analytics_enabled', event.target.checked)} label="Analytics enabled" />
</div>
</div>
<Field label="Editorial Notes" help="Internal editorial context for campaign planning, curation rationale, and staff handoff.">
@@ -2800,19 +2730,9 @@ export default function CollectionManage() {
</div>
<form onSubmit={handleInviteMember} className="mt-5 flex flex-col gap-3 xl:flex-row xl:items-start">
<input value={inviteUsername} onChange={(event) => setInviteUsername(event.target.value)} placeholder="username" className="min-w-0 flex-1 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]" />
<select value={inviteRole} onChange={(event) => setInviteRole(event.target.value)} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35">
<option value="editor">Editor</option>
<option value="contributor">Contributor</option>
<option value="viewer">Viewer</option>
</select>
<NovaSelect value={inviteRole} onChange={(val) => setInviteRole(val)} searchable={false} options={[{ value: 'editor', label: 'Editor' }, { value: 'contributor', label: 'Contributor' }, { value: 'viewer', label: 'Viewer' }]} />
<div className="flex min-w-0 flex-1 flex-col gap-2 md:min-w-[240px]">
<select value={inviteExpiryMode} onChange={(event) => setInviteExpiryMode(event.target.value)} className="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 ({inviteExpiryDays} days)</option>
{inviteExpiryOptions.map((days) => (
<option key={days} value={String(days)}>{days} day{days === 1 ? '' : 's'}</option>
))}
<option value="custom">Custom date</option>
</select>
<NovaSelect value={inviteExpiryMode} onChange={(val) => setInviteExpiryMode(val)} searchable={false} options={[{ value: 'default', label: `Default (${inviteExpiryDays} days)` }, ...inviteExpiryOptions.map((days) => ({ value: String(days), label: `${days} day${days === 1 ? '' : 's'}` })), { value: 'custom', label: 'Custom date' }]} />
{inviteExpiryMode === 'custom' ? (
<input type="datetime-local" value={inviteCustomExpiry} onChange={(event) => setInviteCustomExpiry(event.target.value)} className="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]" />
) : (
@@ -3116,16 +3036,12 @@ export default function CollectionManage() {
<div className="mt-6 flex flex-col gap-3 xl:flex-row xl:items-end">
<div className="min-w-0 flex-1">
<label className="mb-2 block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Add manageable collection</label>
<select
<NovaSelect
value={selectedLinkedCollectionId}
onChange={(event) => setSelectedLinkedCollectionId(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="">Select a collection</option>
{linkedCollectionOptions.map((item) => (
<option key={item.id} value={item.id}>{item.title}</option>
))}
</select>
onChange={(value) => setSelectedLinkedCollectionId(value)}
options={linkedCollectionOptions.map((item) => ({ value: String(item.id), label: item.title }))}
placeholder="Select a collection"
/>
</div>
<button
type="button"
@@ -3186,37 +3102,33 @@ export default function CollectionManage() {
<div className="mt-6 grid gap-3 xl:grid-cols-[180px_minmax(0,1fr)_240px_auto] xl:items-end">
<div>
<label className="mb-2 block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Entity type</label>
<select
<NovaSelect
value={selectedEntityType}
onChange={(event) => {
const nextType = event.target.value
const nextOptions = Array.isArray(entityLinkOptions[nextType]) ? entityLinkOptions[nextType] : []
setSelectedEntityType(nextType)
setSelectedEntityId(nextOptions[0]?.id || '')
onChange={(value) => {
const nextOptions = Array.isArray(entityLinkOptions[value]) ? entityLinkOptions[value] : []
setSelectedEntityType(value)
setSelectedEntityId(String(nextOptions[0]?.id || ''))
}}
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">Creator</option>
<option value="artwork">Artwork</option>
<option value="story">Story</option>
<option value="category">Category</option>
<option value="campaign">Campaign</option>
<option value="event">Event</option>
<option value="tag">Tag or Theme</option>
</select>
options={[
{ value: 'creator', label: 'Creator' },
{ value: 'artwork', label: 'Artwork' },
{ value: 'story', label: 'Story' },
{ value: 'category', label: 'Category' },
{ value: 'campaign', label: 'Campaign' },
{ value: 'event', label: 'Event' },
{ value: 'tag', label: 'Tag or Theme' },
]}
searchable={false}
/>
</div>
<div className="min-w-0">
<label className="mb-2 block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Choose entity</label>
<select
<NovaSelect
value={selectedEntityId}
onChange={(event) => setSelectedEntityId(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="">Select an entity</option>
{(entityLinkOptions[selectedEntityType] || []).map((item) => (
<option key={`${selectedEntityType}-${item.id}`} value={item.id}>{item.label}</option>
))}
</select>
onChange={(value) => setSelectedEntityId(value)}
options={(entityLinkOptions[selectedEntityType] || []).map((item) => ({ value: String(item.id), label: item.label }))}
placeholder="Select an entity"
/>
</div>
<div className="min-w-0">
<label className="mb-2 block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Relationship label</label>
@@ -3319,31 +3231,32 @@ export default function CollectionManage() {
<div className="mt-6 grid gap-5 xl:grid-cols-2">
<div className="rounded-[24px] border border-white/10 bg-white/[0.04] p-5">
<Field label="Moderation Status">
<select
<NovaSelect
value={collectionState?.moderation_status || 'active'}
onChange={(event) => handleModerationStatusChange(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="active">Active</option>
<option value="under_review">Under review</option>
<option value="restricted">Restricted</option>
<option value="hidden">Hidden</option>
</select>
onChange={(value) => handleModerationStatusChange(value)}
options={[
{ value: 'active', label: 'Active' },
{ value: 'under_review', label: 'Under review' },
{ value: 'restricted', label: 'Restricted' },
{ value: 'hidden', label: 'Hidden' },
]}
searchable={false}
/>
</Field>
<div className="mt-4 space-y-3">
<label className="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<div className="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<span>Allow comments</span>
<input type="checkbox" checked={form.allow_comments} onChange={(event) => handleModerationToggle('allow_comments', event.target.checked)} />
</label>
<label className="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<Checkbox checked={form.allow_comments} onChange={(event) => handleModerationToggle('allow_comments', event.target.checked)} />
</div>
<div className="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<span>Allow submissions</span>
<input type="checkbox" checked={form.allow_submissions} onChange={(event) => handleModerationToggle('allow_submissions', event.target.checked)} />
</label>
<label className="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<Checkbox checked={form.allow_submissions} onChange={(event) => handleModerationToggle('allow_submissions', event.target.checked)} />
</div>
<div className="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">
<span>Allow saves</span>
<input type="checkbox" checked={form.allow_saves} onChange={(event) => handleModerationToggle('allow_saves', event.target.checked)} />
</label>
<Checkbox checked={form.allow_saves} onChange={(event) => handleModerationToggle('allow_saves', event.target.checked)} />
</div>
</div>
</div>