feat: anchor header/footer, editable header buttons, campaign management, image uploads, badge font customization, and brevo export fix
This commit is contained in:
1 parent
3dc5778155
commit
2b4d3643cb
10 files changed
+1819
-178
No files matched your search
@@ -25,6 +25,46 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
currentPriceSub = '99',
|
||||
currentUnitPrice = '1kg = 4,91 €'
|
||||
}) => {
|
||||
const POPULAR_FONTS = [
|
||||
{ name: 'Poppins', source: 'google' as const },
|
||||
{ name: 'Montserrat', source: 'google' as const },
|
||||
{ name: 'Oswald', source: 'google' as const },
|
||||
{ name: 'Bebas Neue', source: 'google' as const },
|
||||
{ name: 'Anton', source: 'google' as const },
|
||||
{ name: 'Roboto Condensed', source: 'google' as const },
|
||||
{ name: 'Barlow Semi Condensed', source: 'google' as const },
|
||||
{ name: 'Inter', source: 'google' as const },
|
||||
{ name: 'Open Sans', source: 'google' as const },
|
||||
{ name: 'Arial Narrow', source: 'system' as const },
|
||||
{ name: 'Impact', source: 'system' as const }
|
||||
];
|
||||
|
||||
const loadFontDynamically = (fontFamily: string, source: 'system' | 'google' | 'adobe' = 'google', url?: string) => {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
if (source === 'adobe' && url) {
|
||||
const existing = document.querySelector(`link[data-adobe-font="${url}"]`);
|
||||
if (!existing) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = url;
|
||||
link.setAttribute('data-adobe-font', url);
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
} else if (source === 'google' && fontFamily) {
|
||||
const clean = fontFamily.trim().replace(/\s+/g, '+');
|
||||
const href = `https://fonts.googleapis.com/css2?family=${clean}:wght@400;600;700;800;900&display=swap`;
|
||||
const existing = document.querySelector(`link[data-google-font="${clean}"]`);
|
||||
if (!existing) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = href;
|
||||
link.setAttribute('data-google-font', clean);
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [badges, setBadges] = useState<BadgeTemplate[]>([]);
|
||||
const [activeBadge, setActiveBadge] = useState<BadgeTemplate | null>(null);
|
||||
const [selectedLayerId, setSelectedLayerId] = useState<string | null>(null);
|
||||
@@ -32,6 +72,17 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
const [isExportingPng, setIsExportingPng] = useState(false);
|
||||
const [isSavingDb, setIsSavingDb] = useState(false);
|
||||
|
||||
// Načítanie písiem pre všetky vrstvy aktívnej cenovky
|
||||
useEffect(() => {
|
||||
if (activeBadge?.layersConfig) {
|
||||
activeBadge.layersConfig.forEach((l) => {
|
||||
if (l.fontFamily) {
|
||||
loadFontDynamically(l.fontFamily, l.fontSource || 'google', l.fontUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [activeBadge]);
|
||||
|
||||
// Načítanie existujúcich šablón zo Supabase
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -366,9 +417,11 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
top: `${layer.y}px`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: `${layer.fontSize}px`,
|
||||
fontWeight: layer.fontWeight as any,
|
||||
fontWeight: (layer.fontWeight || '700') as any,
|
||||
color: layer.fill,
|
||||
fontFamily: 'Poppins, Arial, sans-serif',
|
||||
fontFamily: layer.fontFamily
|
||||
? `'${layer.fontFamily}', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`
|
||||
: 'Poppins, Arial, sans-serif',
|
||||
whiteSpace: 'nowrap',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
@@ -530,6 +583,181 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Font nastavenie */}
|
||||
<div className="space-y-2 pt-2 border-t border-gray-200">
|
||||
<label className="block text-[11px] font-semibold text-gray-700">
|
||||
Písmo (Font) a Hrúbka
|
||||
</label>
|
||||
|
||||
{/* Zdroj písma */}
|
||||
<div className="grid grid-cols-3 gap-1 bg-gray-200 p-0.5 rounded-lg text-[10px] font-bold text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
updateActiveLayer({ fontSource: 'google' });
|
||||
}}
|
||||
className={`py-1 rounded ${
|
||||
(activeLayer.fontSource || 'google') === 'google'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
Google
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
updateActiveLayer({ fontSource: 'adobe' });
|
||||
}}
|
||||
className={`py-1 rounded ${
|
||||
activeLayer.fontSource === 'adobe'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
Adobe
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
updateActiveLayer({ fontSource: 'system' });
|
||||
}}
|
||||
className={`py-1 rounded ${
|
||||
activeLayer.fontSource === 'system'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
Systém
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Google Fonts voľba */}
|
||||
{(activeLayer.fontSource || 'google') === 'google' && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">Výber Google Fontu</label>
|
||||
<select
|
||||
value={
|
||||
POPULAR_FONTS.some((f) => f.name === (activeLayer.fontFamily || 'Poppins'))
|
||||
? activeLayer.fontFamily || 'Poppins'
|
||||
: 'custom'
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (e.target.value !== 'custom') {
|
||||
loadFontDynamically(e.target.value, 'google');
|
||||
updateActiveLayer({ fontFamily: e.target.value, fontSource: 'google' });
|
||||
}
|
||||
}}
|
||||
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white font-medium"
|
||||
>
|
||||
{POPULAR_FONTS.filter((f) => f.source === 'google').map((f) => (
|
||||
<option key={f.name} value={f.name}>
|
||||
{f.name}
|
||||
</option>
|
||||
))}
|
||||
<option value="custom">Iný Google Font (zadať vlastný)...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{(!POPULAR_FONTS.some((f) => f.name === (activeLayer.fontFamily || 'Poppins')) ||
|
||||
activeLayer.fontFamily === 'custom') && (
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">Názov Google Fontu</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="napr. Montserrat, Oswald, Syne"
|
||||
value={activeLayer.fontFamily === 'custom' ? '' : activeLayer.fontFamily || ''}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
loadFontDynamically(val, 'google');
|
||||
updateActiveLayer({ fontFamily: val, fontSource: 'google' });
|
||||
}}
|
||||
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Adobe Fonts voľba */}
|
||||
{activeLayer.fontSource === 'adobe' && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">
|
||||
Adobe Typekit CSS URL
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://use.typekit.net/xxxx.css"
|
||||
value={activeLayer.fontUrl || ''}
|
||||
onChange={(e) => {
|
||||
const url = e.target.value;
|
||||
if (activeLayer.fontFamily) {
|
||||
loadFontDynamically(activeLayer.fontFamily, 'adobe', url);
|
||||
}
|
||||
updateActiveLayer({ fontUrl: url, fontSource: 'adobe' });
|
||||
}}
|
||||
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white font-mono text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">
|
||||
Názov rodiny písma (Font Family)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="napr. futura-pt, omnes-pro"
|
||||
value={activeLayer.fontFamily || ''}
|
||||
onChange={(e) => {
|
||||
const family = e.target.value;
|
||||
if (activeLayer.fontUrl) {
|
||||
loadFontDynamically(family, 'adobe', activeLayer.fontUrl);
|
||||
}
|
||||
updateActiveLayer({ fontFamily: family, fontSource: 'adobe' });
|
||||
}}
|
||||
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Systémové písma */}
|
||||
{activeLayer.fontSource === 'system' && (
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">Systémový font</label>
|
||||
<select
|
||||
value={activeLayer.fontFamily || 'Arial'}
|
||||
onChange={(e) => updateActiveLayer({ fontFamily: e.target.value, fontSource: 'system' })}
|
||||
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white"
|
||||
>
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Arial Narrow">Arial Narrow</option>
|
||||
<option value="Impact">Impact</option>
|
||||
<option value="Trebuchet MS">Trebuchet MS</option>
|
||||
<option value="Helvetica">Helvetica</option>
|
||||
<option value="Verdana">Verdana</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hrúbka fontu */}
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">Hrúbka písma (Weight)</label>
|
||||
<select
|
||||
value={activeLayer.fontWeight || '700'}
|
||||
onChange={(e) => updateActiveLayer({ fontWeight: e.target.value })}
|
||||
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white font-medium"
|
||||
>
|
||||
<option value="400">400 - Normal</option>
|
||||
<option value="600">600 - SemiBold</option>
|
||||
<option value="700">700 - Bold</option>
|
||||
<option value="800">800 - ExtraBold</option>
|
||||
<option value="900">900 - Black</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label className="block text-[10px] text-gray-500 mb-0.5">Pozícia X ({activeLayer.x}px)</label>
|
||||
|
||||
Reference in new issue
Block a user