feat(products-ui): import upload screen (§5.3, PUC-2/5a)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,58 @@
|
||||
// Import upload (SD-0002 §5.3) — stub; Task 12 replaces this with the dropzone.
|
||||
// Import — upload (SD-0002 §5.3). Selecting a file starts upload + validation
|
||||
// immediately (PUC-2); file-level rejections (PUC-5a) render in place with the
|
||||
// picker live for retry. No notifications — errors render here.
|
||||
import { useRef, useState } from "react";
|
||||
import { uploadImport } from "../../productsApi";
|
||||
import { Banner } from "../../ui/kit";
|
||||
|
||||
export default function ImportUpload() {
|
||||
return null;
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
async function onPick(files: FileList | null) {
|
||||
const file = files?.[0];
|
||||
if (!file) return;
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
const resp = await uploadImport(file);
|
||||
setBusy(false);
|
||||
if (inputRef.current) inputRef.current.value = "";
|
||||
if (!resp.ok) {
|
||||
setError(resp.error.message);
|
||||
return;
|
||||
}
|
||||
window.location.hash = `#/products/imports/drafts/${resp.value.id}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="products products--narrow">
|
||||
<p className="note">
|
||||
<a href="#/products">← Products</a>
|
||||
</p>
|
||||
<h1>Import products</h1>
|
||||
{error && (
|
||||
<Banner tone="attn" title="That file can't be imported">
|
||||
{error}
|
||||
</Banner>
|
||||
)}
|
||||
<label className={`dropzone${busy ? " dropzone--busy" : ""}`}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept=".csv,text/csv"
|
||||
disabled={busy}
|
||||
onChange={(e) => void onPick(e.target.files)}
|
||||
/>
|
||||
<span className="dropzone__title">{busy ? "Validating…" : "Choose a CSV file"}</span>
|
||||
<span className="note">CSV, up to 5,000 rows</span>
|
||||
</label>
|
||||
<p className="note">
|
||||
Works with the canonical format.{" "}
|
||||
<a href="/api/products/sample.csv" download>
|
||||
Download sample CSV
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,14 @@
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dropzone__title {
|
||||
display: block;
|
||||
font-family: var(--wv-font-display);
|
||||
font-weight: var(--weight-medium);
|
||||
font-size: 17px;
|
||||
color: var(--text-on-dark-soft);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* ── small screens ──────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 720px) {
|
||||
|
||||
Reference in New Issue
Block a user