feat(media): add mvp1 upload pipeline baseline
This commit is contained in:
163
apps/admin/src/components/media/media-upload-form.tsx
Normal file
163
apps/admin/src/components/media/media-upload-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user