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:
@@ -4,8 +4,8 @@ import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'
|
||||
/**
|
||||
* UploadOverlay
|
||||
*
|
||||
* A frosted-glass floating panel that rises from the bottom of the step content
|
||||
* area while an upload or processing job is in flight.
|
||||
* A centered modal-style progress overlay shown while an upload or processing
|
||||
* job is in flight.
|
||||
*
|
||||
* Shows:
|
||||
* - State icon + label + live percentage
|
||||
@@ -109,107 +109,135 @@ export default function UploadOverlay({
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
key="upload-overlay"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={`${meta.label}${progress > 0 ? ` — ${progress}%` : ''}`}
|
||||
initial={prefersReducedMotion ? false : { opacity: 0, y: 24, scale: 0.98 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0, y: 16, scale: 0.98 }}
|
||||
initial={prefersReducedMotion ? false : { opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0 }}
|
||||
transition={overlayTransition}
|
||||
className="absolute inset-x-0 bottom-0 z-30 pointer-events-none"
|
||||
className="fixed inset-0 z-[80] flex items-center justify-center p-4 sm:p-6"
|
||||
>
|
||||
{/* Fade-out gradient so step content peeks through above */}
|
||||
<div
|
||||
className="absolute inset-x-0 -top-12 h-12 bg-gradient-to-t from-slate-950/70 to-transparent pointer-events-none rounded-b-2xl"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-slate-950/72 backdrop-blur-sm" aria-hidden="true" />
|
||||
|
||||
<motion.div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="upload-overlay-title"
|
||||
aria-describedby="upload-overlay-description"
|
||||
initial={prefersReducedMotion ? false : { opacity: 0, y: 18, scale: 0.97 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0, y: 12, scale: 0.98 }}
|
||||
transition={overlayTransition}
|
||||
className="relative w-full max-w-xl overflow-hidden rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(6,14,24,0.96),rgba(2,8,23,0.92))] px-5 pb-5 pt-5 shadow-[0_30px_120px_rgba(2,8,23,0.72)] ring-1 ring-inset ring-white/8 backdrop-blur-xl sm:px-6 sm:pb-6 sm:pt-6"
|
||||
>
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={`${meta.label}${progress > 0 ? ` — ${progress}%` : ''}`}
|
||||
>
|
||||
<div className="mb-4 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className={`flex items-center gap-2 ${meta.color}`}>
|
||||
{meta.icon}
|
||||
<span id="upload-overlay-title" className="text-xl font-semibold tracking-tight">
|
||||
{meta.label}
|
||||
</span>
|
||||
{machineState !== 'error' && (
|
||||
<span className="relative flex h-2.5 w-2.5 shrink-0" aria-hidden="true">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-current opacity-50" />
|
||||
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-current opacity-80" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p id="upload-overlay-description" className="mt-2 text-sm text-white/60">
|
||||
{machineState === 'error'
|
||||
? 'The upload was interrupted. You can retry safely or start over.'
|
||||
: 'Keep this tab open while we finish the upload and process your artwork.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="pointer-events-auto mx-0 rounded-b-2xl rounded-t-xl border border-white/10 bg-slate-950/88 px-5 pb-5 pt-4 shadow-2xl shadow-black/70 ring-1 ring-inset ring-white/6 backdrop-blur-xl">
|
||||
{/* ── Header: icon + state label + percentage ── */}
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className={`flex items-center gap-2 ${meta.color}`}>
|
||||
{meta.icon}
|
||||
<span className="text-sm font-semibold tracking-wide">
|
||||
{meta.label}
|
||||
</span>
|
||||
{/* Pulsing dot for active states */}
|
||||
{machineState !== 'error' && (
|
||||
<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-current opacity-50" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-current opacity-80" />
|
||||
<span className={`shrink-0 tabular-nums text-2xl font-bold ${meta.color}`}>
|
||||
{progress}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{machineState !== 'error' && (
|
||||
<span className={`tabular-nums text-sm font-bold ${meta.color}`}>
|
||||
{progress}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 p-4 sm:p-5">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className={`text-lg font-semibold ${meta.color}`}>
|
||||
{meta.label}
|
||||
</span>
|
||||
{machineState !== 'error' && (
|
||||
<span className="text-sm text-white/45">Secure pipeline active</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── Progress bar ── */}
|
||||
<div className="mt-3 h-2 w-full overflow-hidden rounded-full bg-white/8">
|
||||
<motion.div
|
||||
className={`h-full rounded-full bg-gradient-to-r ${meta.barColor}`}
|
||||
animate={{ width: machineState === 'error' ? '100%' : `${progress}%` }}
|
||||
transition={barTransition}
|
||||
style={machineState === 'error' ? { opacity: 0.35 } : {}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 h-3 w-full overflow-hidden rounded-full bg-white/8">
|
||||
<motion.div
|
||||
className={`h-full rounded-full bg-gradient-to-r ${meta.barColor}`}
|
||||
animate={{ width: machineState === 'error' ? '100%' : `${progress}%` }}
|
||||
transition={barTransition}
|
||||
style={machineState === 'error' ? { opacity: 0.35 } : {}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── Sublabel / transparency message ── */}
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{machineState !== 'error' && displayLabel && (
|
||||
<motion.p
|
||||
key={displayLabel}
|
||||
initial={prefersReducedMotion ? false : { opacity: 0, y: 4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0, y: -4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="mt-2 text-xs text-white/50"
|
||||
>
|
||||
{displayLabel}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{machineState !== 'error' && displayLabel && (
|
||||
<motion.p
|
||||
key={displayLabel}
|
||||
initial={prefersReducedMotion ? false : { opacity: 0, y: 4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0, y: -4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="mt-4 text-sm text-white/60"
|
||||
>
|
||||
{displayLabel}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* ── Error details + actions ── */}
|
||||
<AnimatePresence initial={false}>
|
||||
{machineState === 'error' && (
|
||||
<motion.div
|
||||
key="error-block"
|
||||
initial={prefersReducedMotion ? false : { opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="mt-3 rounded-lg border border-rose-400/20 bg-rose-500/10 px-3 py-2.5">
|
||||
<p className="text-xs text-rose-200 leading-relaxed">
|
||||
{error || 'Something went wrong. You can retry safely.'}
|
||||
</p>
|
||||
<div className="mt-2.5 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRetry}
|
||||
className="rounded-md border border-rose-300/30 bg-rose-400/15 px-3 py-1 text-xs font-medium text-rose-100 transition hover:bg-rose-400/25 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-300/60"
|
||||
>
|
||||
Retry upload
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
className="rounded-md border border-white/20 bg-white/8 px-3 py-1 text-xs font-medium text-white/60 transition hover:bg-white/14 hover:text-white/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
||||
>
|
||||
Start over
|
||||
</button>
|
||||
{machineState !== 'error' && (
|
||||
<p className="mt-2 text-xs uppercase tracking-[0.2em] text-white/30">
|
||||
Progress updates are live
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<AnimatePresence initial={false}>
|
||||
{machineState === 'error' && (
|
||||
<motion.div
|
||||
key="error-block"
|
||||
initial={prefersReducedMotion ? false : { opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={prefersReducedMotion ? {} : { opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="mt-4 rounded-2xl border border-rose-400/20 bg-rose-500/10 px-4 py-4">
|
||||
<p className="text-sm leading-relaxed text-rose-100">
|
||||
{error || 'Something went wrong. You can retry safely.'}
|
||||
</p>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRetry}
|
||||
className="rounded-lg border border-rose-300/30 bg-rose-400/15 px-3.5 py-2 text-sm font-medium text-rose-100 transition hover:bg-rose-400/25 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-300/60"
|
||||
>
|
||||
Retry upload
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
className="rounded-lg border border-white/20 bg-white/8 px-3.5 py-2 text-sm font-medium text-white/70 transition hover:bg-white/14 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
||||
>
|
||||
Start over
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
Reference in New Issue
Block a user