Save workspace changes
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import React from 'react'
|
||||
import ArchiveScreenshotPicker from '../ArchiveScreenshotPicker'
|
||||
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,
|
||||
selectedScreenshotIndex,
|
||||
screenshotErrors,
|
||||
screenshotPerFileErrors,
|
||||
onScreenshotsChange,
|
||||
onSelectedScreenshotChange,
|
||||
// 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}
|
||||
/>
|
||||
|
||||
{isArchive && screenshots.length > 0 && (
|
||||
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-4 sm:p-5">
|
||||
<ArchiveScreenshotPicker
|
||||
screenshots={screenshots}
|
||||
selectedIndex={selectedScreenshotIndex}
|
||||
onSelect={onSelectedScreenshotChange}
|
||||
compact
|
||||
title="Choose default screenshot"
|
||||
description="Pick the screenshot that should be uploaded as the archive preview before you start the upload."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user