webstack-author/src/components/admin/PublicationFilePicker.tsx
2026-08-18 16:07:15 +02:00

62 lines
2.7 KiB
TypeScript

import { useRef, useState } from "react";
import { FileText, Loader2, Trash2, Upload } from "lucide-react";
import { adminApi, AdminApiError } from "../../api/adminApi";
interface PublicationFilePickerProps {
format: "pdf" | "epub";
value: string;
onChange: (url: string) => void;
}
export default function PublicationFilePicker({ format, value, onChange }: PublicationFilePickerProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const upload = (file?: File) => {
if (!file) return;
if (!file.name.toLowerCase().endsWith(`.${format}`)) {
setError(`Bitte eine ${format.toUpperCase()}-Datei auswählen.`);
return;
}
if (file.size > 10 * 1024 * 1024) {
setError("Die Datei darf maximal 10 MB groß sein.");
return;
}
setLoading(true);
setError("");
const reader = new FileReader();
reader.onload = async () => {
try {
const result = await adminApi.uploadPublication(file.name, String(reader.result), format);
onChange(result.url);
} catch (uploadError) {
setError(uploadError instanceof AdminApiError ? uploadError.message : "Upload fehlgeschlagen.");
} finally {
setLoading(false);
}
};
reader.onerror = () => {
setError("Die Datei konnte nicht gelesen werden.");
setLoading(false);
};
reader.readAsDataURL(file);
};
return (
<div className="space-y-2 rounded-xl border border-slate-800 bg-slate-950/50 p-3">
<input ref={inputRef} type="file" accept={format === "pdf" ? "application/pdf,.pdf" : "application/epub+zip,.epub"} className="hidden" onChange={(event) => upload(event.target.files?.[0])} />
{value ? (
<div className="flex items-center justify-between gap-2">
<span className="min-w-0 truncate text-xs text-slate-300"><FileText className="mr-1 inline h-4 w-4" />{value.split("/").pop()}</span>
<button type="button" onClick={() => onChange("")} className="p-1.5 text-slate-400 hover:text-rose-400" aria-label={`${format.toUpperCase()} entfernen`}><Trash2 className="h-4 w-4" /></button>
</div>
) : (
<button type="button" disabled={loading} onClick={() => inputRef.current?.click()} className="flex w-full items-center justify-center gap-2 rounded-lg bg-indigo-600 px-3 py-2 text-xs font-semibold text-white hover:bg-indigo-500 disabled:opacity-60">
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Upload className="h-4 w-4" />}<span>{format.toUpperCase()} hochladen</span>
</button>
)}
{error && <p role="alert" className="text-xs text-rose-400">{error}</p>}
</div>
);
}