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

@@ -1,6 +1,8 @@
import React from 'react'
import { Head, usePage } from '@inertiajs/react'
import CollectionCard from '../../components/profile/collections/CollectionCard'
import Checkbox from '../../components/ui/Checkbox'
import NovaSelect from '../../components/ui/NovaSelect'
function getCsrfToken() {
if (typeof document === 'undefined') return ''
@@ -401,13 +403,13 @@ export default function CollectionStaffSurfaces() {
<div className="mt-6 grid gap-4 md:grid-cols-2">
<Field label="Surface Key" help={definitionForm.id ? 'Surface keys stay stable during edits so existing placements remain attached.' : null}><input value={definitionForm.surface_key} onChange={(event) => setDefinitionForm((current) => ({ ...current, surface_key: event.target.value }))} disabled={Boolean(definitionForm.id)} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none disabled:cursor-not-allowed disabled:opacity-60" maxLength={120} /></Field>
<Field label="Title"><input value={definitionForm.title} onChange={(event) => setDefinitionForm((current) => ({ ...current, 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" maxLength={160} /></Field>
<Field label="Mode"><select value={definitionForm.mode} onChange={(event) => setDefinitionForm((current) => ({ ...current, mode: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none"><option value="manual">Manual</option><option value="automatic">Automatic</option><option value="hybrid">Hybrid</option></select></Field>
<Field label="Ranking"><select value={definitionForm.ranking_mode} onChange={(event) => setDefinitionForm((current) => ({ ...current, ranking_mode: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none"><option value="ranking_score">Ranking score</option><option value="recent_activity">Recent activity</option><option value="quality_score">Quality score</option></select></Field>
<Field label="Mode"><NovaSelect value={definitionForm.mode} onChange={(val) => setDefinitionForm((current) => ({ ...current, mode: val }))} searchable={false} options={[{ value: 'manual', label: 'Manual' }, { value: 'automatic', label: 'Automatic' }, { value: 'hybrid', label: 'Hybrid' }]} /></Field>
<Field label="Ranking"><NovaSelect value={definitionForm.ranking_mode} onChange={(val) => setDefinitionForm((current) => ({ ...current, ranking_mode: val }))} searchable={false} options={[{ value: 'ranking_score', label: 'Ranking score' }, { value: 'recent_activity', label: 'Recent activity' }, { value: 'quality_score', label: 'Quality score' }]} /></Field>
<Field label="Max Items"><input type="number" min="1" max="24" value={definitionForm.max_items} onChange={(event) => setDefinitionForm((current) => ({ ...current, max_items: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" /></Field>
<Field label="Starts At" help="Optional activation window for the full surface definition."><input type="datetime-local" value={definitionForm.starts_at} onChange={(event) => setDefinitionForm((current) => ({ ...current, starts_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" /></Field>
<Field label="Ends At" help="Leave blank when the surface should stay live until staff changes it."><input type="datetime-local" value={definitionForm.ends_at} onChange={(event) => setDefinitionForm((current) => ({ ...current, ends_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" /></Field>
<Field label="Fallback Surface Key" help="Optional fallback when this definition is inactive, scheduled out, or resolves no items."><input value={definitionForm.fallback_surface_key} onChange={(event) => setDefinitionForm((current) => ({ ...current, fallback_surface_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" maxLength={120} /></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={definitionForm.is_active} onChange={(event) => setDefinitionForm((current) => ({ ...current, is_active: event.target.checked }))} />Active</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={definitionForm.is_active} onChange={(event) => setDefinitionForm((current) => ({ ...current, is_active: event.target.checked }))} label="Active" /></div>
</div>
<Field label="Description" help="Operational note for staff browsing this surface later."><textarea value={definitionForm.description} onChange={(event) => setDefinitionForm((current) => ({ ...current, description: event.target.value }))} className="mt-4 min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" maxLength={400} /></Field>
<Field label="Rules JSON" help="Supported filters include campaign, event, season, type, presentation_style, theme_token, collaboration_mode, owner_username or owner_usernames, commercial_eligible_only, analytics_enabled_only, min_quality_score, min_ranking_score, include_collection_ids, exclude_collection_ids, and featured_only."><textarea value={definitionForm.rules_json} onChange={(event) => setDefinitionForm((current) => ({ ...current, rules_json: event.target.value }))} className="mt-4 min-h-[160px] w-full rounded-2xl border border-white/10 bg-slate-950/50 px-4 py-3 font-mono text-sm text-white outline-none" spellCheck={false} /></Field>
@@ -423,14 +425,14 @@ export default function CollectionStaffSurfaces() {
<h2 className="mt-2 text-2xl font-semibold text-white">Manual and campaign slots</h2>
</div>
<div className="mt-6 grid gap-4 md:grid-cols-2">
<Field label="Surface"><select value={placementForm.surface_key} onChange={(event) => setPlacementForm((current) => ({ ...current, surface_key: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none">{surfaceKeyOptions.map((option) => <option key={option} value={option}>{option}</option>)}</select></Field>
<Field label="Collection"><select value={placementForm.collection_id} onChange={(event) => setPlacementForm((current) => ({ ...current, collection_id: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none">{collectionOptions.map((option) => <option key={option.id} value={option.id}>{option.title}</option>)}</select></Field>
<Field label="Placement Type"><select value={placementForm.placement_type} onChange={(event) => setPlacementForm((current) => ({ ...current, placement_type: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none"><option value="manual">Manual</option><option value="campaign">Campaign</option><option value="scheduled_override">Scheduled override</option></select></Field>
<Field label="Surface"><NovaSelect value={placementForm.surface_key} onChange={(val) => setPlacementForm((current) => ({ ...current, surface_key: val }))} options={surfaceKeyOptions.map((o) => ({ value: o, label: o }))} /></Field>
<Field label="Collection"><NovaSelect value={String(placementForm.collection_id || '')} onChange={(val) => setPlacementForm((current) => ({ ...current, collection_id: val }))} options={collectionOptions.map((o) => ({ value: String(o.id), label: o.title }))} /></Field>
<Field label="Placement Type"><NovaSelect value={placementForm.placement_type} onChange={(val) => setPlacementForm((current) => ({ ...current, placement_type: val }))} searchable={false} options={[{ value: 'manual', label: 'Manual' }, { value: 'campaign', label: 'Campaign' }, { value: 'scheduled_override', label: 'Scheduled override' }]} /></Field>
<Field label="Priority"><input type="number" min="-100" max="100" value={placementForm.priority} onChange={(event) => setPlacementForm((current) => ({ ...current, priority: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" /></Field>
<Field label="Starts At"><input type="datetime-local" value={placementForm.starts_at} onChange={(event) => setPlacementForm((current) => ({ ...current, starts_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" /></Field>
<Field label="Ends At"><input type="datetime-local" value={placementForm.ends_at} onChange={(event) => setPlacementForm((current) => ({ ...current, ends_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" /></Field>
<Field label="Campaign Key" help="Optional campaign label for reporting and grouped overrides."><input value={placementForm.campaign_key} onChange={(event) => setPlacementForm((current) => ({ ...current, 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" maxLength={80} /></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={placementForm.is_active} onChange={(event) => setPlacementForm((current) => ({ ...current, is_active: event.target.checked }))} />Active placement</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={placementForm.is_active} onChange={(event) => setPlacementForm((current) => ({ ...current, is_active: event.target.checked }))} label="Active placement" /></div>
</div>
<Field label="Notes" help="Internal note for why this collection owns the slot."><textarea value={placementForm.notes} onChange={(event) => setPlacementForm((current) => ({ ...current, notes: event.target.value }))} className="mt-4 min-h-[110px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" maxLength={1000} /></Field>
<div className="mt-5 flex flex-wrap items-center gap-3">
@@ -459,7 +461,7 @@ export default function CollectionStaffSurfaces() {
const checked = batchForm.collection_ids.includes(option.id)
return (
<label key={option.id} className={`flex cursor-pointer items-start gap-3 rounded-[22px] border px-4 py-3 transition ${checked ? 'border-lime-300/30 bg-lime-400/10' : 'border-white/10 bg-white/[0.04] hover:bg-white/[0.07]'}`}>
<input type="checkbox" checked={checked} onChange={() => toggleBatchCollection(option.id)} className="mt-1" />
<Checkbox checked={checked} onChange={() => toggleBatchCollection(option.id)} />
<span className="min-w-0">
<span className="block truncate text-sm font-semibold text-white">{option.title}</span>
<span className="mt-1 block text-xs text-slate-400">{option.type || 'collection'} · {option.visibility || 'public'}</span>
@@ -487,10 +489,10 @@ export default function CollectionStaffSurfaces() {
<p className="text-sm font-semibold text-white">Optional placement plan</p>
<p className="mt-2 text-sm text-slate-300">If you set a surface, the preview shows which collections can safely be placed and which ones will be skipped.</p>
<div className="mt-4 grid gap-4 md:grid-cols-2">
<Field label="Surface"><select value={batchForm.surface_key} onChange={(event) => setBatchForm((current) => ({ ...current, surface_key: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none"><option value="">No placement</option>{surfaceKeyOptions.map((option) => <option key={option} value={option}>{option}</option>)}</select></Field>
<Field label="Placement Type"><select value={batchForm.placement_type} onChange={(event) => setBatchForm((current) => ({ ...current, placement_type: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none"><option value="campaign">Campaign</option><option value="manual">Manual</option><option value="scheduled_override">Scheduled override</option></select></Field>
<Field label="Surface"><NovaSelect value={batchForm.surface_key} onChange={(val) => setBatchForm((current) => ({ ...current, surface_key: val }))} placeholder="No placement" options={surfaceKeyOptions.map((o) => ({ value: o, label: o }))} /></Field>
<Field label="Placement Type"><NovaSelect value={batchForm.placement_type} onChange={(val) => setBatchForm((current) => ({ ...current, placement_type: val }))} searchable={false} options={[{ value: 'campaign', label: 'Campaign' }, { value: 'manual', label: 'Manual' }, { value: 'scheduled_override', label: 'Scheduled override' }]} /></Field>
<Field label="Priority"><input type="number" min="-100" max="100" value={batchForm.priority} onChange={(event) => setBatchForm((current) => ({ ...current, priority: event.target.value }))} className="w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" /></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={batchForm.is_active} onChange={(event) => setBatchForm((current) => ({ ...current, is_active: event.target.checked }))} />Active placement</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={batchForm.is_active} onChange={(event) => setBatchForm((current) => ({ ...current, is_active: event.target.checked }))} label="Active placement" /></div>
<Field label="Starts At"><input type="datetime-local" value={batchForm.starts_at} onChange={(event) => setBatchForm((current) => ({ ...current, starts_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" /></Field>
<Field label="Ends At"><input type="datetime-local" value={batchForm.ends_at} onChange={(event) => setBatchForm((current) => ({ ...current, ends_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" /></Field>
</div>