Files
Coop-NL/src/components/editor/ImageUploadInput.tsx
T

180 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
};