180 lines
6.3 KiB
TypeScript
180 lines
6.3 KiB
TypeScript
'use client';
|
||
|
||
import React, { useRef, useState } from 'react';
|
||
import { Upload, Loader2, Image as ImageIcon } from 'lucide-react';
|
||
import { shrinkImage, shrinkRemoteImage, 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 uploadImage = async (file: File) => {
|
||
setIsUploading(true);
|
||
try {
|
||
const small = await shrinkImage(file);
|
||
onChange(await uploadFile(small, small.name));
|
||
} catch (err) {
|
||
alert(`Chyba pri nahrávaní obrázka: ${(err as Error).message}`);
|
||
} finally {
|
||
setIsUploading(false);
|
||
if (fileInputRef.current) {
|
||
fileInputRef.current.value = '';
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const file = e.target.files?.[0];
|
||
if (file) uploadImage(file);
|
||
};
|
||
|
||
// Vložená URL: príliš veľký obrázok sa zmenší a uloží k nám (posledná spracovaná adresa, aby sa nerobilo dvakrát)
|
||
const lastCheckedUrl = useRef('');
|
||
const [urlNote, setUrlNote] = useState<{ tone: 'ok' | 'warn'; text: string } | null>(null);
|
||
const checkUrl = async (url: string) => {
|
||
url = url.trim();
|
||
if (!url || url === lastCheckedUrl.current) return;
|
||
lastCheckedUrl.current = url;
|
||
setIsUploading(true);
|
||
setUrlNote(null);
|
||
try {
|
||
const result = await shrinkRemoteImage(url);
|
||
if (result.status === 'shrunk') {
|
||
lastCheckedUrl.current = result.url;
|
||
onChange(result.url);
|
||
setUrlNote({ tone: 'ok', text: 'Obrázok bol väčší ako 1000 px – zmenšili sme ho a uložili k nám.' });
|
||
} else if (result.status === 'small') {
|
||
setUrlNote({ tone: 'ok', text: 'Obrázok je v poriadku (najviac 1000 px), adresa ostáva.' });
|
||
} else if (result.status === 'login') {
|
||
setUrlNote({
|
||
tone: 'warn',
|
||
text: 'Obrázok je za prihlásením – v e-maile sa nezobrazí. Otvorte ho v prehliadači, pravým klikom „Kopírovať obrázok“ a sem vložte Ctrl+V.'
|
||
});
|
||
} else if (result.status === 'failed') {
|
||
setUrlNote({ tone: 'warn', text: `Obrázok sa nepodarilo stiahnuť (${result.error}). Skontrolujte adresu, prípadne obrázok skopírujte a vložte Ctrl+V.` });
|
||
}
|
||
} catch (err) {
|
||
setUrlNote({ tone: 'warn', text: `Obrázok sa nepodarilo spracovať: ${(err as Error).message}` });
|
||
} finally {
|
||
setIsUploading(false);
|
||
}
|
||
};
|
||
|
||
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
|
||
// obrázok zo schránky (Ctrl+V) → nahrá sa ako súbor
|
||
const file = [...e.clipboardData.files].find((f) => f.type.startsWith('image/'));
|
||
if (file) {
|
||
e.preventDefault();
|
||
uploadImage(file);
|
||
return;
|
||
}
|
||
const text = e.clipboardData.getData('text');
|
||
if (/^https?:\/\//i.test(text.trim())) setTimeout(() => checkUrl(text), 0);
|
||
};
|
||
|
||
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) => {
|
||
setUrlNote(null);
|
||
onChange(e.target.value);
|
||
}}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') checkUrl(e.currentTarget.value);
|
||
}}
|
||
onPaste={handlePaste}
|
||
onBlur={(e) => checkUrl(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>
|
||
|
||
{urlNote && (
|
||
<p className={`text-[10px] leading-snug ${urlNote.tone === 'warn' ? 'text-amber-700 bg-amber-50 border border-amber-200 rounded-md px-2 py-1' : 'text-emerald-700'}`}>
|
||
{urlNote.text}
|
||
</p>
|
||
)}
|
||
|
||
{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>
|
||
);
|
||
};
|