164 lines
5.3 KiB
TypeScript
164 lines
5.3 KiB
TypeScript
"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>
|
|
)
|
|
}
|