Refactor dashboard and upload flows
Remove dead admin UI code, redesign dashboard followers/following and upload experiences, and add schema audit tooling with repair migrations for forum and upload drift.
This commit is contained in:
@@ -49,6 +49,7 @@ export default function PublishPanel({
|
||||
scheduledAt = null,
|
||||
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
visibility = 'public', // 'public' | 'unlisted' | 'private'
|
||||
showRightsConfirmation = true,
|
||||
onPublishModeChange,
|
||||
onScheduleAt,
|
||||
onVisibilityChange,
|
||||
@@ -93,8 +94,26 @@ export default function PublishPanel({
|
||||
|
||||
const rightsError = uploadReady && !hasRights ? 'Rights confirmation is required.' : null
|
||||
|
||||
const visibilityOptions = [
|
||||
{
|
||||
value: 'public',
|
||||
label: 'Public',
|
||||
hint: 'Visible to everyone',
|
||||
},
|
||||
{
|
||||
value: 'unlisted',
|
||||
label: 'Unlisted',
|
||||
hint: 'Available by direct link',
|
||||
},
|
||||
{
|
||||
value: 'private',
|
||||
label: 'Private',
|
||||
hint: 'Keep as draft visibility',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="bg-panel/80 backdrop-blur rounded-2xl shadow-xl shadow-black/40 ring-1 ring-white/10 p-5 space-y-5 h-fit">
|
||||
<div className="h-fit space-y-5 rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-5 shadow-[0_24px_90px_rgba(2,8,23,0.28)] backdrop-blur">
|
||||
{/* Preview + title */}
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Thumbnail */}
|
||||
@@ -139,24 +158,45 @@ export default function PublishPanel({
|
||||
<div className="border-t border-white/8" />
|
||||
|
||||
{/* Readiness checklist */}
|
||||
<ReadinessChecklist items={checklist} />
|
||||
<div className="rounded-2xl border border-white/8 bg-white/[0.03] p-4">
|
||||
<ReadinessChecklist items={checklist} />
|
||||
</div>
|
||||
|
||||
{/* Visibility */}
|
||||
<div>
|
||||
<label className="block text-[10px] uppercase tracking-wider text-white/40 mb-1.5" htmlFor="publish-visibility">
|
||||
<label className="mb-2 block text-[10px] uppercase tracking-wider text-white/40" htmlFor="publish-visibility">
|
||||
Visibility
|
||||
</label>
|
||||
<select
|
||||
id="publish-visibility"
|
||||
value={visibility}
|
||||
onChange={(e) => onVisibilityChange?.(e.target.value)}
|
||||
disabled={!canPublish && machineState !== 'ready_to_publish'}
|
||||
className="w-full appearance-none rounded-lg border border-white/15 bg-white/8 px-3 py-1.5 text-sm text-white focus:outline-none focus:ring-1 focus:ring-sky-400/60 disabled:opacity-50"
|
||||
>
|
||||
<option value="public">Public</option>
|
||||
<option value="unlisted">Unlisted</option>
|
||||
<option value="private">Private (draft)</option>
|
||||
</select>
|
||||
<div id="publish-visibility" className="grid gap-2">
|
||||
{visibilityOptions.map((option) => {
|
||||
const active = visibility === option.value
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => onVisibilityChange?.(option.value)}
|
||||
disabled={!canPublish && machineState !== 'ready_to_publish'}
|
||||
className={[
|
||||
'flex items-start justify-between gap-3 rounded-2xl border px-4 py-3 text-left transition disabled:opacity-50',
|
||||
active
|
||||
? 'border-sky-300/30 bg-sky-400/10 text-white'
|
||||
: 'border-white/10 bg-white/[0.03] text-white/75 hover:border-white/20 hover:bg-white/[0.06]',
|
||||
].join(' ')}
|
||||
>
|
||||
<div>
|
||||
<div className="text-sm font-semibold">{option.label}</div>
|
||||
<div className="mt-1 text-xs text-white/50">{option.hint}</div>
|
||||
</div>
|
||||
<span className={[
|
||||
'mt-0.5 inline-flex h-5 w-5 items-center justify-center rounded-full border text-[10px]',
|
||||
active ? 'border-sky-300/40 bg-sky-400/20 text-sky-100' : 'border-white/10 bg-white/5 text-white/35',
|
||||
].join(' ')}>
|
||||
{active ? '✓' : ''}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Schedule picker – only shows when upload is ready */}
|
||||
@@ -171,20 +211,21 @@ export default function PublishPanel({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Rights confirmation (required before publish) */}
|
||||
<div>
|
||||
<Checkbox
|
||||
id="publish-rights-confirm"
|
||||
checked={Boolean(metadata.rightsAccepted)}
|
||||
onChange={(event) => onToggleRights?.(event.target.checked)}
|
||||
variant="emerald"
|
||||
size={18}
|
||||
label={<span className="text-xs text-white/85">I confirm I own the rights to this content.</span>}
|
||||
hint={<span className="text-[11px] text-white/50">Required before publishing.</span>}
|
||||
error={rightsError}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{showRightsConfirmation && (
|
||||
<div>
|
||||
<Checkbox
|
||||
id="publish-rights-confirm"
|
||||
checked={Boolean(metadata.rightsAccepted)}
|
||||
onChange={(event) => onToggleRights?.(event.target.checked)}
|
||||
variant="emerald"
|
||||
size={18}
|
||||
label={<span className="text-xs text-white/85">I confirm I own the rights to this content.</span>}
|
||||
hint={<span className="text-[11px] text-white/50">Required before publishing.</span>}
|
||||
error={rightsError}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Primary action button */}
|
||||
<button
|
||||
@@ -193,7 +234,7 @@ export default function PublishPanel({
|
||||
onClick={() => onPublish?.()}
|
||||
title={!canPublish ? 'Complete all requirements first' : undefined}
|
||||
className={[
|
||||
'w-full rounded-xl py-2.5 text-sm font-semibold transition',
|
||||
'w-full rounded-2xl py-3 text-sm font-semibold transition',
|
||||
canSchedulePublish && !isPublishing
|
||||
? publishMode === 'schedule'
|
||||
? 'bg-violet-500/80 text-white hover:bg-violet-500 shadow-[0_4px_16px_rgba(139,92,246,0.25)]'
|
||||
|
||||
Reference in New Issue
Block a user