first
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { useEffect, useRef, useState, type FormEvent } from "react";
|
||||
import { apiFetch, apiUpload } from "../../api/client";
|
||||
import type { Media } from "../../api/types";
|
||||
|
||||
export default function MediaPage() {
|
||||
const [media, setMedia] = useState<Media[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [altText, setAltText] = useState("");
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
function load() {
|
||||
setLoading(true);
|
||||
apiFetch<{ media: Media[] }>("/api/admin/media")
|
||||
.then((data) => setMedia(data.media))
|
||||
.catch(() => setError("Failed to load media."))
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
useEffect(load, []);
|
||||
|
||||
async function handleUpload(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
const file = fileInputRef.current?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
setUploading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("alt_text", altText);
|
||||
await apiUpload("/api/admin/media", formData);
|
||||
setAltText("");
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
load();
|
||||
} catch {
|
||||
setError("Upload failed (check file type/size).");
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
if (!confirm("Delete this media file?")) return;
|
||||
try {
|
||||
await apiFetch(`/api/admin/media/${id}`, { method: "DELETE" });
|
||||
load();
|
||||
} catch {
|
||||
setError("Failed to delete media.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Media</h1>
|
||||
{error && <div className="alert alert-error">{error}</div>}
|
||||
|
||||
<div className="panel">
|
||||
<h2>Upload</h2>
|
||||
<form onSubmit={handleUpload}>
|
||||
<div className="field">
|
||||
<label htmlFor="file">File (image or video)</label>
|
||||
<input id="file" type="file" ref={fileInputRef} accept="image/*,video/mp4,video/webm" required />
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="alt-text">Alt text</label>
|
||||
<input id="alt-text" value={altText} onChange={(e) => setAltText(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-actions">
|
||||
<button type="submit" className="btn btn-primary" disabled={uploading}>
|
||||
{uploading ? "Uploading…" : "Upload"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="page-loading">Loading…</div>
|
||||
) : (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))", gap: "1rem" }}>
|
||||
{media.map((m) => (
|
||||
<div key={m.id} className="panel" style={{ padding: "0.75rem", marginBottom: 0 }}>
|
||||
{m.mime_type.startsWith("image/") ? (
|
||||
<img src={m.url} alt={m.alt_text} style={{ width: "100%", height: 100, objectFit: "cover", borderRadius: 4 }} />
|
||||
) : (
|
||||
<div style={{ height: 100, display: "flex", alignItems: "center", justifyContent: "center", background: "#f3f4f6" }}>
|
||||
{m.mime_type}
|
||||
</div>
|
||||
)}
|
||||
<p style={{ fontSize: "0.75rem", margin: "0.4rem 0", wordBreak: "break-all" }}>{m.filename}</p>
|
||||
<button className="btn btn-danger" style={{ width: "100%" }} onClick={() => handleDelete(m.id)}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user