feat(media): add mvp1 upload pipeline baseline

This commit is contained in:
2026-02-12 11:57:39 +01:00
parent ad351ed73a
commit 5becba602c
8 changed files with 450 additions and 157 deletions

View File

@@ -0,0 +1,163 @@
"use client"
import { Button } from "@cms/ui/button"
import { type FormEvent, useState } from "react"
type MediaType = "artwork" | "banner" | "promotion" | "video" | "gif" | "generic"
const ACCEPT_BY_TYPE: Record<MediaType, string> = {
artwork: "image/jpeg,image/png,image/webp,image/avif,image/gif",
banner: "image/jpeg,image/png,image/webp,image/avif",
promotion: "image/jpeg,image/png,image/webp,image/avif,image/gif,video/mp4,video/webm",
video: "video/mp4,video/webm,video/quicktime",
gif: "image/gif",
generic: "image/*,video/*",
}
export function MediaUploadForm() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const [mediaType, setMediaType] = useState<MediaType>("artwork")
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
const form = event.currentTarget
const formData = new FormData(form)
setError(null)
setIsSubmitting(true)
try {
const response = await fetch("/api/media/upload", {
method: "POST",
body: formData,
})
if (!response.ok) {
const payload = (await response.json().catch(() => null)) as {
message?: string
} | null
setError(payload?.message ?? "Upload failed. Please verify file and metadata.")
return
}
const payload = (await response.json().catch(() => null)) as {
notice?: string
} | null
const notice = payload?.notice ?? "Media uploaded."
window.location.href = `/media?notice=${encodeURIComponent(notice)}`
} catch {
setError("Upload request failed. Please retry.")
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} className="mt-4 space-y-3">
{error ? (
<p className="rounded border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
{error}
</p>
) : null}
<div className="grid gap-3 md:grid-cols-2">
<label className="space-y-1">
<span className="text-xs text-neutral-600">Title</span>
<input
name="title"
required
minLength={1}
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Type</span>
<select
name="type"
value={mediaType}
onChange={(event) => setMediaType(event.target.value as MediaType)}
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
>
<option value="artwork">artwork</option>
<option value="banner">banner</option>
<option value="promotion">promotion</option>
<option value="video">video</option>
<option value="gif">gif</option>
<option value="generic">generic</option>
</select>
</label>
</div>
<label className="space-y-1">
<span className="text-xs text-neutral-600">File</span>
<input
name="file"
type="file"
required
accept={ACCEPT_BY_TYPE[mediaType]}
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Description</span>
<textarea
name="description"
rows={3}
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<div className="grid gap-3 md:grid-cols-2">
<label className="space-y-1">
<span className="text-xs text-neutral-600">Alt text</span>
<input
name="altText"
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Author</span>
<input
name="author"
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
</div>
<div className="grid gap-3 md:grid-cols-2">
<label className="space-y-1">
<span className="text-xs text-neutral-600">Source</span>
<input
name="source"
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Copyright</span>
<input
name="copyright"
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
</div>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Tags (comma-separated)</span>
<input name="tags" className="w-full rounded border border-neutral-300 px-3 py-2 text-sm" />
</label>
<label className="inline-flex items-center gap-2 text-sm text-neutral-700">
<input name="isPublished" type="checkbox" value="true" className="size-4" />
Publish immediately
</label>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Uploading..." : "Upload media"}
</Button>
</form>
)
}