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.
142 lines
5.9 KiB
JavaScript
142 lines
5.9 KiB
JavaScript
import React from 'react'
|
||
import { motion, useReducedMotion } from 'framer-motion'
|
||
|
||
/**
|
||
* StudioStatusBar
|
||
*
|
||
* Sticky header beneath the main nav that shows:
|
||
* - Step pills (reuse UploadStepper visual style but condensed)
|
||
* - Upload progress bar (visible while uploading/processing)
|
||
* - Machine-state pill
|
||
* - Back / Next primary actions
|
||
*/
|
||
const STATE_LABELS = {
|
||
idle: null,
|
||
initializing: 'Initializing…',
|
||
uploading: 'Uploading',
|
||
finishing: 'Finishing…',
|
||
processing: 'Processing',
|
||
ready_to_publish: 'Ready',
|
||
publishing: 'Publishing…',
|
||
complete: 'Published',
|
||
error: 'Error',
|
||
cancelled: 'Cancelled',
|
||
}
|
||
|
||
const STATE_COLORS = {
|
||
idle: '',
|
||
initializing: 'bg-sky-500/20 text-sky-200 border-sky-300/30',
|
||
uploading: 'bg-sky-500/25 text-sky-100 border-sky-300/40',
|
||
finishing: 'bg-sky-400/20 text-sky-200 border-sky-300/30',
|
||
processing: 'bg-amber-500/20 text-amber-100 border-amber-300/30',
|
||
ready_to_publish: 'bg-emerald-500/20 text-emerald-100 border-emerald-300/35',
|
||
publishing: 'bg-sky-500/25 text-sky-100 border-sky-300/40',
|
||
complete: 'bg-emerald-500/25 text-emerald-100 border-emerald-300/50',
|
||
error: 'bg-red-500/20 text-red-200 border-red-300/30',
|
||
cancelled: 'bg-white/8 text-white/50 border-white/15',
|
||
}
|
||
|
||
export default function StudioStatusBar({
|
||
steps = [],
|
||
activeStep = 1,
|
||
highestUnlockedStep = 1,
|
||
machineState = 'idle',
|
||
progress = 0,
|
||
showProgress = false,
|
||
onStepClick,
|
||
}) {
|
||
const prefersReducedMotion = useReducedMotion()
|
||
const transition = prefersReducedMotion ? { duration: 0 } : { duration: 0.3, ease: 'easeOut' }
|
||
const stateLabel = STATE_LABELS[machineState] ?? machineState
|
||
const stateColor = STATE_COLORS[machineState] ?? 'bg-white/8 text-white/50 border-white/15'
|
||
|
||
return (
|
||
<div className="sticky top-0 z-20 -mx-4 px-4 pb-0 pt-2 sm:-mx-6 sm:px-6">
|
||
{/* Blur backdrop */}
|
||
<div className="absolute inset-0 bg-slate-950/80 backdrop-blur-md" aria-hidden="true" />
|
||
|
||
<div className="relative overflow-hidden rounded-[24px] border border-white/8 bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))] px-3 shadow-[0_14px_44px_rgba(2,8,23,0.24)] sm:px-4">
|
||
{/* Step pills row */}
|
||
<nav aria-label="Upload steps">
|
||
<ol className="flex flex-nowrap items-center gap-2 overflow-x-auto py-3 pr-1 sm:gap-3">
|
||
{steps.map((step, index) => {
|
||
const number = index + 1
|
||
const isActive = number === activeStep
|
||
const isComplete = number < activeStep
|
||
const isLocked = number > highestUnlockedStep
|
||
const canNavigate = !isLocked && number < activeStep
|
||
|
||
const btnClass = [
|
||
'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] sm:text-xs transition',
|
||
isActive
|
||
? 'border-sky-300/70 bg-sky-500/25 text-white shadow-[0_10px_30px_rgba(14,165,233,0.14)]'
|
||
: isComplete
|
||
? 'border-emerald-300/30 bg-emerald-500/15 text-emerald-100 hover:bg-emerald-500/25 cursor-pointer'
|
||
: isLocked
|
||
? 'cursor-default border-white/10 bg-white/5 text-white/35 pointer-events-none'
|
||
: 'border-white/15 bg-white/6 text-white/70 hover:bg-white/12 cursor-pointer',
|
||
].join(' ')
|
||
|
||
const circleClass = isComplete
|
||
? 'border-emerald-300/50 bg-emerald-500/20 text-emerald-100'
|
||
: isActive
|
||
? 'border-sky-300/50 bg-sky-500/25 text-white'
|
||
: 'border-white/20 bg-white/6 text-white/60'
|
||
|
||
return (
|
||
<li key={step.key} className="flex shrink-0 items-center gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => canNavigate && onStepClick?.(number)}
|
||
disabled={isLocked}
|
||
aria-disabled={isLocked}
|
||
aria-current={isActive ? 'step' : undefined}
|
||
className={btnClass}
|
||
>
|
||
<span className={`grid h-4 w-4 place-items-center rounded-full border text-[10px] shrink-0 ${circleClass}`}>
|
||
{isComplete ? '✓' : number}
|
||
</span>
|
||
<span className="whitespace-nowrap">{step.label}</span>
|
||
</button>
|
||
{index < steps.length - 1 && (
|
||
<span className="text-white/30 select-none text-xs" aria-hidden="true">›</span>
|
||
)}
|
||
</li>
|
||
)
|
||
})}
|
||
|
||
{/* Spacer */}
|
||
<li className="flex-1" aria-hidden="true" />
|
||
|
||
{/* State pill */}
|
||
{stateLabel && (
|
||
<li className="shrink-0">
|
||
<span className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] ${stateColor}`}>
|
||
{['uploading', 'initializing', 'finishing', 'processing', 'publishing'].includes(machineState) && (
|
||
<span className="relative flex h-2 w-2 shrink-0" aria-hidden="true">
|
||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-300 opacity-60" />
|
||
<span className="relative inline-flex h-2 w-2 rounded-full bg-sky-300" />
|
||
</span>
|
||
)}
|
||
{stateLabel}
|
||
</span>
|
||
</li>
|
||
)}
|
||
</ol>
|
||
</nav>
|
||
|
||
{/* Progress bar (shown during upload/processing) */}
|
||
{showProgress && (
|
||
<div className="mb-2 h-1 w-full overflow-hidden rounded-full bg-white/8">
|
||
<motion.div
|
||
className="h-full rounded-full bg-gradient-to-r from-sky-400 via-cyan-300 to-emerald-300"
|
||
animate={{ width: `${progress}%` }}
|
||
transition={transition}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|