feat: automaticke alt texty s moznostou rucneho prepisu (uloha 14)

- produkt: nazov produktu, ostatne obrazky: nadpis okna / nazov modulu
- z alt textu sa odstrania HTML znacky (napr. <br> v nadpisoch)
- pri kazdom obrazku pole Alt text; ak je vyplnene, ma prednost

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-19 10:24:58 +02:00
1 parent 23ac4dc830
commit d43a1dae40
5 files changed
+96 -12

No files matched your search

+44
View File
@@ -5,6 +5,7 @@ import { NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter
import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe, Palette, Check } from 'lucide-react';
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
import { ImageUploadInput } from './ImageUploadInput';
import { pickAlt } from '@/lib/altText';
const PRODUCT_BG_PRESETS = [
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
@@ -143,6 +144,9 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
label="Logo COOP Jednota"
value={module.data.logoUrl || ''}
onChange={(url) => updateData({ logoUrl: url })}
altValue={module.data.logoAlt}
onAltChange={(alt) => updateData({ logoAlt: alt })}
altPlaceholder="COOP Jednota"
/>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Odkaz loga</label>
@@ -254,6 +258,9 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
label="Obrázok bannera"
value={module.data.imageUrl || ''}
onChange={(url) => updateData({ imageUrl: url })}
altValue={module.data.altText}
onAltChange={(alt) => updateData({ altText: alt })}
altPlaceholder={pickAlt(module.data.title, module.title, 'COOP Jednota')}
/>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Odkaz bannera</label>
@@ -536,6 +543,13 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
products[pIdx].imageUrl = url;
updateData({ products });
}}
altValue={p.imageAlt}
onAltChange={(alt) => {
const products = [...module.data.products];
products[pIdx].imageAlt = alt;
updateData({ products });
}}
altPlaceholder={pickAlt(p.title, module.data.sectionTitle, 'Produkt')}
placeholder="https://...produkt.png alebo nahrajte súbor"
/>
@@ -947,6 +961,9 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
label="Obrázok banneru Vernostných zliav"
value={module.data.headerImageUrl || ''}
onChange={(url) => updateData({ headerImageUrl: url })}
altValue={module.data.headerImageAlt}
onAltChange={(alt) => updateData({ headerImageAlt: alt })}
altPlaceholder={pickAlt(module.data.title, module.data.sectionTitle, 'Vernostné zľavy')}
/>
<div>
@@ -1095,6 +1112,13 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
products[pIdx].imageUrl = url;
updateData({ products });
}}
altValue={p.imageAlt}
onAltChange={(alt) => {
const products = [...module.data.products];
products[pIdx].imageAlt = alt;
updateData({ products });
}}
altPlaceholder={pickAlt(p.title, module.data.sectionTitle, 'Produkt')}
placeholder="https://...produkt.png alebo nahrajte súbor"
/>
@@ -1585,6 +1609,11 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onChange={(url) => updateData({
item1: { ...module.data.item1, imageUrl: url }
})}
altValue={module.data.item1?.altText}
onAltChange={(alt) => updateData({
item1: { ...module.data.item1, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item1?.title, module.data.title, module.title)}
/>
</div>
</div>
@@ -1625,6 +1654,11 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onChange={(url) => updateData({
item2: { ...module.data.item2, imageUrl: url }
})}
altValue={module.data.item2?.altText}
onAltChange={(alt) => updateData({
item2: { ...module.data.item2, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item2?.title, module.data.title, module.title)}
/>
</div>
</div>
@@ -1666,6 +1700,11 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onChange={(url) => updateData({
item1: { ...module.data.item1, imageUrl: url }
})}
altValue={module.data.item1?.altText}
onAltChange={(alt) => updateData({
item1: { ...module.data.item1, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item1?.title, module.data.title, module.title)}
/>
</div>
</div>
@@ -1704,6 +1743,11 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onChange={(url) => updateData({
item2: { ...module.data.item2, imageUrl: url }
})}
altValue={module.data.item2?.altText}
onAltChange={(alt) => updateData({
item2: { ...module.data.item2, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item2?.title, module.data.title, module.title)}
/>
</div>
</div>
+19 -1
View File
@@ -8,13 +8,20 @@ interface ImageUploadInputProps {
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'
placeholder = 'https://... alebo nahrajte súbor',
altValue,
onAltChange,
altPlaceholder
}) => {
const fileInputRef = useRef<HTMLInputElement>(null);
const [isUploading, setIsUploading] = useState(false);
@@ -107,6 +114,17 @@ export const ImageUploadInput: React.FC<ImageUploadInputProps> = ({
)}
</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>
);
};