Files
Coop-NL/src/components/editor/ImageUploadInput.tsx
T
Jakub_HostacnyandClaude Opus 5.5 17c852e2bf fix: nahravanie velkych obrazkov - priamo do Supabase cez podpisanu URL, zmensenie na 2000 px
Vercel odmietal poziadavky nad 4,5 MB (Request Entity Too Large) a bucket
berie najviac 10 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:29:29 +02:00

120 lines
3.7 KiB
TypeScript

'use client';
import React, { useRef, useState } from 'react';
import { Upload, Loader2, Image as ImageIcon } from 'lucide-react';
import { shrinkImage, uploadFile } from '@/lib/upload';
interface ImageUploadInputProps {
label?: string;
value: string;
onChange: (url: string) => void;
placeholder?: string;
// Voliteľný ručný alt text; prázdny = použije sa automatický (altPlaceholder)
altValue?: string;
onAltChange?: (alt: string) => void;
altPlaceholder?: string;
}
export const ImageUploadInput: React.FC<ImageUploadInputProps> = ({
label = 'Obrázok',
value,
onChange,
placeholder = 'https://... alebo nahrajte súbor',
altValue,
onAltChange,
altPlaceholder
}) => {
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 small = await shrinkImage(file);
onChange(await uploadFile(small, small.name));
} catch (err: any) {
alert(`Chyba pri nahrávaní obrázka: ${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 min-w-0 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>
{onAltChange && (
<input
type="text"
value={altValue || ''}
onChange={(e) => onAltChange(e.target.value)}
placeholder={altPlaceholder ? `Alt text (automaticky: ${altPlaceholder})` : 'Alt text'}
title="Alt text obrázka. Ak ho nevyplníte, doplní sa automaticky."
className="w-full text-[11px] border border-gray-200 rounded-lg px-2 py-1.5 bg-white placeholder:text-gray-400"
/>
)}
</div>
);
};