Files
SkinbaseNova/resources/js/components/upload/steps/Step1FileUpload.jsx
2026-03-28 19:15:39 +01:00

145 lines
6.4 KiB
JavaScript

import React from 'react'
import UploadDropzone from '../UploadDropzone'
import ScreenshotUploader from '../ScreenshotUploader'
/**
* Step1FileUpload
*
* Step 1 of the upload wizard: file selection + live upload progress.
* Shows the dropzone, optional screenshot uploader (archives),
* and the progress panel once an upload is in flight.
*/
export default function Step1FileUpload({
headingRef,
// File state
primaryFile,
primaryPreviewUrl,
primaryErrors,
primaryWarnings,
fileMetadata,
fileSelectionLocked,
onPrimaryFileChange,
// Archive screenshots
isArchive,
screenshots,
screenshotErrors,
screenshotPerFileErrors,
onScreenshotsChange,
// Machine state (passed for potential future use)
machine,
}) {
const fileSelected = Boolean(primaryFile)
return (
<div className="space-y-6 rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-6 shadow-[0_24px_90px_rgba(2,8,23,0.28)] backdrop-blur sm:p-8">
{/* ── Hero heading ─────────────────────────────────────────────────── */}
<div className="text-center">
<span className="inline-flex items-center gap-1.5 rounded-full border border-sky-400/30 bg-sky-400/10 px-3 py-1 text-[11px] uppercase tracking-widest text-sky-300">
Step 1 of 3
</span>
<h2
ref={headingRef}
tabIndex={-1}
className="mt-4 text-2xl font-bold text-white focus:outline-none"
>
Upload your artwork
</h2>
<p className="mx-auto mt-2 max-w-md text-sm text-white/55">
Drop an image or an archive pack. We validate the file instantly so you can start uploading straight away.
</p>
</div>
{/* ── Locked notice ────────────────────────────────────────────────── */}
{fileSelectionLocked && (
<div className="flex items-center gap-2.5 rounded-2xl bg-amber-500/10 px-4 py-3 text-sm text-amber-100 ring-1 ring-amber-300/30">
<svg className="h-4 w-4 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fillRule="evenodd" d="M10 1a4.5 4.5 0 00-4.5 4.5V9H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-.5V5.5A4.5 4.5 0 0010 1zm3 8V5.5a3 3 0 10-6 0V9h6z" clipRule="evenodd" />
</svg>
File is locked after upload starts. Reset to change the file.
</div>
)}
{/* ── Primary dropzone ─────────────────────────────────────────────── */}
<UploadDropzone
title="Drop your file here"
description="JPG, PNG, WEBP · ZIP, RAR, 7Z · Images up to 50 MB · Archives up to 200 MB"
fileName={primaryFile?.name || ''}
previewUrl={primaryPreviewUrl}
fileMeta={fileMetadata}
fileHint="No file selected"
invalid={primaryErrors.length > 0}
errors={primaryErrors}
showLooksGood={fileSelected && primaryErrors.length === 0}
looksGoodText="File looks good — ready to upload"
locked={fileSelectionLocked}
onPrimaryFileChange={(file) => {
if (fileSelectionLocked) return
onPrimaryFileChange(file || null)
}}
/>
{/* ── Screenshots (archives only) ──────────────────────────────────── */}
<ScreenshotUploader
title="Archive screenshots"
description="Add at least 1 screenshot so we can generate a thumbnail and analyze your content."
visible={isArchive}
files={screenshots}
min={1}
max={5}
perFileErrors={screenshotPerFileErrors}
errors={screenshotErrors}
invalid={isArchive && screenshotErrors.length > 0}
showLooksGood={isArchive && screenshots.length > 0 && screenshotErrors.length === 0}
looksGoodText="Screenshots look good"
onFilesChange={onScreenshotsChange}
/>
{/* ── Subtle what-happens-next hints (shown only before a file is picked) */}
{!fileSelected && (
<div className="grid gap-3 sm:grid-cols-3">
{[
{
icon: (
<svg className="h-5 w-5 text-sky-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 15v4a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-4" /><path d="M7 10l5-5 5 5" /><path d="M12 5v10" />
</svg>
),
label: 'Add your file',
hint: 'Image or archive — drop it in or click to browse.',
},
{
icon: (
<svg className="h-5 w-5 text-violet-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M9 12l2 2 4-4" /><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10z" />
</svg>
),
label: 'Instant validation',
hint: 'Format, size, and screenshot checks run immediately.',
},
{
icon: (
<svg className="h-5 w-5 text-emerald-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M5 13l4 4L19 7" />
</svg>
),
label: 'Start upload',
hint: 'One click sends your file through the secure pipeline.',
},
].map((item) => (
<div key={item.label} className="flex items-start gap-3 rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-4">
<div className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-white/10 bg-white/[0.05]">
{item.icon}
</div>
<div>
<p className="text-sm font-semibold text-white">{item.label}</p>
<p className="mt-1 text-xs leading-5 text-slate-400">{item.hint}</p>
</div>
</div>
))}
</div>
)}
</div>
)
}