145 lines
6.4 KiB
JavaScript
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>
|
|
)
|
|
}
|