Voeg CMS voor de Publieke Website toe (overgenomen van rikx-org)
Pagina-editor, hoofdmenu, SEO en media onder Publieke Website → Pagina's (/website), los van het CMS in rikx-org: eigen tabellen in de beheerdatabase. Blokken (hero, tekstblok, testimonial, voordelenlijst, call-to-action) met Quill, NL/EN/DE met terugval op NL, en afbeeldingsupload naar MEDIA_DIR met een openbare /media-route (CSP-sandbox). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import { useId, useRef, useState } from "react";
|
||||
import { uploadMediaAction } from "./actions";
|
||||
import { errorClass, inputClass, labelClass, secondaryButton } from "./ui";
|
||||
|
||||
// Kies een eerder geüploade afbeelding of upload een nieuwe (max 10 MB)
|
||||
export function ImageField({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
mediaImages,
|
||||
emptyLabel = "Geen afbeelding",
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (path: string) => void;
|
||||
mediaImages: string[];
|
||||
emptyLabel?: string;
|
||||
}) {
|
||||
const [uploaded, setUploaded] = useState<string[]>([]);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const fieldId = useId();
|
||||
|
||||
async function handleUpload() {
|
||||
const file = fileInputRef.current?.files?.[0];
|
||||
if (!file) return;
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
setUploading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await uploadMediaAction(formData);
|
||||
if (result.error) setError(result.error);
|
||||
else if (result.path) {
|
||||
setUploaded((u) => [result.path!, ...u]);
|
||||
onChange(result.path);
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
}
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Ook een gekozen afbeelding die niet (meer) in de lijst staat blijft zichtbaar als optie
|
||||
const options = [...new Set([...(value ? [value] : []), ...uploaded, ...mediaImages])];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className={labelClass} htmlFor={fieldId}>
|
||||
{label}
|
||||
</label>
|
||||
<select id={fieldId} value={value} onChange={(e) => onChange(e.target.value)} className={inputClass}>
|
||||
<option value="">{emptyLabel}</option>
|
||||
{options.map((img) => (
|
||||
<option key={img} value={img}>
|
||||
{img.split("/").pop()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{value && (
|
||||
<img src={value} alt="" className="max-w-[200px] max-h-[120px] object-cover rounded-lg border border-zinc-200 dark:border-zinc-700" />
|
||||
)}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<input ref={fileInputRef} type="file" accept="image/png,image/jpeg,image/webp,image/svg+xml" className="text-sm text-zinc-600 dark:text-zinc-400" />
|
||||
<button type="button" className={secondaryButton} onClick={handleUpload} disabled={uploading}>
|
||||
{uploading ? "Bezig..." : "Uploaden"}
|
||||
</button>
|
||||
</div>
|
||||
{error && <p className={errorClass}>{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user