"use client"; import { useRef, useState, type ChangeEvent } from "react"; import { useI18n } from "@/i18n/i18n-provider"; import { readUploadResponse } from "@/lib/upload-client"; interface AdminVideoFieldProps { /** Formularfeld-Name, unter dem der aktuelle Videopfad beim Speichern des * umgebenden
mitgeschickt wird (siehe admin/actions/content.ts). */ name: string; label: string; initialValue: string; } /** * Video-Upload-Feld fuer admin/inhalte (Content-Registry-Feldtyp "video", * aktuell nur home.hero.video). Analog zu AdminImageField: laedt die Datei * sofort per fetch() an app/api/admin/upload-video hoch und haelt den * zurueckgegebenen Pfad in einem versteckten Formularfeld — das eigentliche * Speichern passiert weiterhin ueber das umgebende * . */ export function AdminVideoField({ name, label, initialValue }: AdminVideoFieldProps) { const { t } = useI18n(); const [value, setValue] = useState(initialValue); const [status, setStatus] = useState<"idle" | "uploading" | "error">("idle"); const [errorMessage, setErrorMessage] = useState(""); const inputRef = useRef(null); async function handleFileChange(event: ChangeEvent) { const file = event.target.files?.[0]; if (!file) return; setStatus("uploading"); setErrorMessage(""); try { let response: Response; try { // Die Datei wird als ROHER Body gesendet, nicht als FormData: auf der // Serverseite liest request.formData() den gesamten Body erst // vollstaendig in den Arbeitsspeicher, was bei einem Video bis 100 MB // das Speicherlimit des Pods sprengte (ausfuehrliche Begruendung in // app/api/admin/upload-video/route.ts). Als roher Body laesst er sich // dort direkt auf die Platte durchreichen. fetch() streamt ein // File-Objekt, es landet also auch hier nie am Stueck im Speicher. response = await fetch("/api/admin/upload-video", { method: "POST", headers: { "content-type": file.type || "application/octet-stream" }, body: file, }); } catch { // fetch() wirft nur, wenn der Request den Server gar nicht erreicht // oder die Verbindung abbricht — ein HTTP-Fehlerstatus landet NICHT // hier, sondern wird unten ausgewertet. throw new Error(t.adminVideoField.uploadErrorNetwork); } const result = await readUploadResponse(response); if (!result.ok) { throw new Error( result.kind === "message" ? result.message : t.adminVideoField.uploadErrorStatus(result.status) ); } setValue(result.path); setStatus("idle"); } catch (error: unknown) { setStatus("error"); setErrorMessage(error instanceof Error ? error.message : t.adminVideoField.uploadError); } finally { if (inputRef.current) inputRef.current.value = ""; } } return (
{label}
{value ? (
); }