import React from 'react' 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. * * Shows: * - State icon + label + live percentage * - Thick animated progress bar with gradient * - Processing transparency label (what the backend is doing) * - Error strip with Retry / Reset when something goes wrong */ const ACTIVE_STATES = new Set([ 'initializing', 'uploading', 'finishing', 'processing', ]) const STATE_META = { initializing: { label: 'Initializing', sublabel: 'Preparing your upload…', color: 'text-sky-300', barColor: 'from-sky-500 via-sky-400 to-cyan-300', icon: ( ), }, uploading: { label: 'Uploading', sublabel: 'Sending your file to the server…', color: 'text-sky-300', barColor: 'from-sky-500 via-cyan-400 to-teal-300', icon: ( ), }, finishing: { label: 'Finishing', sublabel: 'Wrapping up the transfer…', color: 'text-cyan-300', barColor: 'from-cyan-500 via-teal-400 to-emerald-300', icon: ( ), }, processing: { label: 'Processing', sublabel: 'Analyzing your artwork…', color: 'text-amber-300', barColor: 'from-amber-500 via-yellow-400 to-lime-300', icon: ( ), }, error: { label: 'Upload failed', sublabel: null, color: 'text-rose-300', barColor: 'from-rose-600 via-rose-500 to-rose-400', icon: ( ), }, } export default function UploadOverlay({ machineState = 'idle', progress = 0, processingLabel = null, error = null, onRetry, onReset, }) { const prefersReducedMotion = useReducedMotion() const isVisible = ACTIVE_STATES.has(machineState) || machineState === 'error' const meta = STATE_META[machineState] ?? STATE_META.uploading const barTransition = prefersReducedMotion ? { duration: 0 } : { duration: 0.4, ease: 'easeOut' } const overlayTransition = prefersReducedMotion ? { duration: 0 } : { duration: 0.25, ease: [0.32, 0.72, 0, 1] } const displayLabel = processingLabel || meta.sublabel return ( {isVisible && ( 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 }} transition={overlayTransition} className="absolute inset-x-0 bottom-0 z-30 pointer-events-none" > {/* Fade-out gradient so step content peeks through above */} {/* ── Header: icon + state label + percentage ── */} {meta.icon} {meta.label} {/* Pulsing dot for active states */} {machineState !== 'error' && ( )} {machineState !== 'error' && ( {progress}% )} {/* ── Progress bar ── */} {/* ── Sublabel / transparency message ── */} {machineState !== 'error' && displayLabel && ( {displayLabel} )} {/* ── Error details + actions ── */} {machineState === 'error' && ( {error || 'Something went wrong. You can retry safely.'} Retry upload Start over )} )} ) }
{error || 'Something went wrong. You can retry safely.'}