feat: vlastne stitky na fotke produktu, Adobe Fonts pre celu aplikaciu
- pri produkte blok Stitky na fotke: text (<br> = novy riadok), poloha X/Y, velkost, natocenie, farba pozadia a textu; lubovolny pocet stitkov - stitky su nad fotkou a pod cenovkou, pismo Aptly Bold; ulozia sa aj do obrazka karty pri exporte - NEXT_PUBLIC_ADOBE_FONTS_URL: odkaz na web projekt Adobe Fonts sa nacita v celej aplikacii (Aptly pre cenovky a stitky) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
332ab833e9
commit
293d9b953c
5 files changed
+131
-3
No files matched your search
@@ -3,7 +3,7 @@
|
||||
// Editor jedného produktu (bežné aj vernostné produkty) – zbalený po produktoch, len to, čo treba:
|
||||
// texty, fotka (zoom/posun), cenovka (šablóna, veľkosť, ceny), dostupnosť a ďalšie možnosti.
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { BadgeTemplate, CardProduct } from '@/types/newsletter';
|
||||
import { BadgeTemplate, CardProduct, ProductLabel } from '@/types/newsletter';
|
||||
import { ChevronDown, Trash2, Pencil, Globe, Layers } from 'lucide-react';
|
||||
import { ImageUploadInput } from './ImageUploadInput';
|
||||
import { pickAlt } from '@/lib/altText';
|
||||
@@ -105,6 +105,72 @@ const ResetLink: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
||||
</button>
|
||||
);
|
||||
|
||||
// Nastavenie jedného vlastného štítka na fotke
|
||||
const LABEL_COLORS: Array<[string, string]> = [
|
||||
['#e2001a', 'Červená'],
|
||||
['#ffde00', 'Žltá'],
|
||||
['#003399', 'Modrá'],
|
||||
['#000000', 'Čierna'],
|
||||
['#ffffff', 'Biela']
|
||||
];
|
||||
|
||||
const LabelEditor: React.FC<{
|
||||
label: ProductLabel;
|
||||
onChange: (patch: Partial<ProductLabel>) => void;
|
||||
onRemove: () => void;
|
||||
}> = ({ label, onChange, onRemove }) => {
|
||||
const swatches = (key: 'bg' | 'color') => (
|
||||
<div className="flex items-center gap-1">
|
||||
{LABEL_COLORS.map(([c, name]) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
title={name}
|
||||
onClick={() => onChange({ [key]: c })}
|
||||
className={`w-4 h-4 rounded-full border ${label[key]?.toLowerCase() === c ? 'ring-2 ring-red-500 border-white' : 'border-black/20'}`}
|
||||
style={{ backgroundColor: c }}
|
||||
/>
|
||||
))}
|
||||
<input
|
||||
type="color"
|
||||
value={label[key] || '#ffffff'}
|
||||
onChange={(e) => onChange({ [key]: e.target.value })}
|
||||
className="w-4 h-4 rounded cursor-pointer border border-gray-300 p-0"
|
||||
title="Vlastná farba"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-2 space-y-1.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<input
|
||||
value={label.text}
|
||||
onChange={(e) => onChange({ text: e.target.value })}
|
||||
placeholder="Text štítka (<br> = nový riadok)"
|
||||
className="flex-1 min-w-0 text-xs font-bold uppercase border border-gray-300 rounded-lg px-2 py-1"
|
||||
/>
|
||||
<button type="button" onClick={onRemove} className="text-gray-300 hover:text-red-600 p-0.5" title="Zmazať štítok">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<Slider label="Vodorovne" value={label.x} display={`${label.x} %`} min={0} max={100} step={1} onChange={(v) => onChange({ x: v })} />
|
||||
<Slider label="Zvisle" value={label.y} display={`${label.y} %`} min={0} max={100} step={1} onChange={(v) => onChange({ y: v })} />
|
||||
<Slider label="Veľkosť" value={label.size} display={`${label.size} px`} min={6} max={40} step={1} onChange={(v) => onChange({ size: v })} />
|
||||
<Slider label="Natočenie" value={label.rotate || 0} display={`${label.rotate || 0}°`} min={-45} max={45} step={1} onChange={(v) => onChange({ rotate: v })} />
|
||||
<div className="grid grid-cols-2 gap-2 pt-0.5">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] font-semibold text-gray-600">Pozadie</span>
|
||||
{swatches('bg')}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] font-semibold text-gray-600">Text</span>
|
||||
{swatches('color')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Šablóna používa ceny s kartou → zobrazíme aj polia s kartou
|
||||
const usesCardPrice = (t?: BadgeTemplate) => !!t && JSON.stringify(t.layersConfig || []).includes('_s_kartou');
|
||||
|
||||
@@ -372,6 +438,39 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
||||
)}
|
||||
</Block>
|
||||
|
||||
{/* VLASTNÉ ŠTÍTKY NA FOTKE */}
|
||||
<Block
|
||||
title="Štítky na fotke"
|
||||
action={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onChange({
|
||||
labels: [
|
||||
...(p.labels || []),
|
||||
{ id: `lbl-${Date.now()}`, text: 'NOVINKA', x: 30, y: 20, size: 12, bg: '#e2001a', color: '#ffffff', rotate: 0 }
|
||||
]
|
||||
})
|
||||
}
|
||||
className="text-[10px] font-bold text-red-700 hover:text-red-900"
|
||||
>
|
||||
+ Pridať štítok
|
||||
</button>
|
||||
}
|
||||
>
|
||||
{(p.labels || []).length === 0 && (
|
||||
<p className="text-[10px] text-gray-400">Napr. „PODIEL MÄSA 92 %“ alebo „NOVINKA“ priamo na fotke produktu.</p>
|
||||
)}
|
||||
{(p.labels || []).map((l, i) => (
|
||||
<LabelEditor
|
||||
key={l.id}
|
||||
label={l}
|
||||
onChange={(patch) => onChange({ labels: (p.labels || []).map((x, j) => (j === i ? { ...x, ...patch } : x)) })}
|
||||
onRemove={() => onChange({ labels: (p.labels || []).filter((_, j) => j !== i) })}
|
||||
/>
|
||||
))}
|
||||
</Block>
|
||||
|
||||
{/* ĎALŠIE MOŽNOSTI */}
|
||||
<details className="group">
|
||||
<summary className="text-[10px] font-bold text-gray-500 uppercase tracking-wider cursor-pointer select-none hover:text-gray-800">
|
||||
|
||||
Reference in new issue
Block a user