feat: anchor header/footer, editable header buttons, campaign management, image uploads, badge font customization, and brevo export fix

This commit is contained in:
Jakub_Hostacny committed 2026-09-24 11:42:45 +02:00
1 parent 3dc5778155
commit 2b4d3643cb
10 files changed
+1819 -178

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
'use client';
import React, { useRef, useState } from 'react';
import { Upload, Loader2, Image as ImageIcon } from 'lucide-react';
interface ImageUploadInputProps {
label?: string;
value: string;
onChange: (url: string) => void;
placeholder?: string;
}
export const ImageUploadInput: React.FC<ImageUploadInputProps> = ({
label = 'Obrázok',
value,
onChange,
placeholder = 'https://... alebo nahrajte súbor'
}) => {
const fileInputRef = useRef<HTMLInputElement>(null);
const [isUploading, setIsUploading] = useState(false);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setIsUploading(true);
try {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success && data.url) {
onChange(data.url);
} else {
alert(`Chyba pri nahrávaní obrázka: ${data.error || 'Neznáma chyba'}`);
}
} catch (err: any) {
alert(`Chyba: ${err.message}`);
} finally {
setIsUploading(false);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}
};
return (
<div className="space-y-1.5">
{label && <label className="block text-[11px] font-semibold text-gray-700">{label}</label>}
<div className="flex items-center gap-2">
{/* Náhľad miniatúry */}
<div className="w-10 h-10 rounded-lg bg-gray-100 border border-gray-200 flex-shrink-0 flex items-center justify-center overflow-hidden">
{value ? (
<img
src={value}
alt="Náhľad"
className="w-full h-full object-contain"
onError={(e) => {
(e.target as HTMLElement).style.display = 'none';
}}
/>
) : (
<ImageIcon className="w-4 h-4 text-gray-400" />
)}
</div>
{/* Textové pole s URL */}
<input
type="text"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="flex-1 text-xs font-mono border border-gray-300 rounded-lg p-2 bg-white"
/>
{/* Tlačidlo nahrávania */}
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept="image/*"
className="hidden"
/>
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={isUploading}
className="px-2.5 py-2 text-xs font-bold text-gray-700 bg-gray-100 hover:bg-gray-200 border border-gray-300 rounded-lg flex items-center gap-1 transition-colors whitespace-nowrap disabled:opacity-50"
title="Nahrať obrázok zo zariadenia"
>
{isUploading ? (
<>
<Loader2 className="w-3.5 h-3.5 animate-spin text-red-600" />
<span>Nahrávam...</span>
</>
) : (
<>
<Upload className="w-3.5 h-3.5" />
<span>Nahrať</span>
</>
)}
</button>
</div>
</div>
);
};