Fix canvas background to exact COOP gradient, remove module borders/gaps, add interactive Badge Editor SVG to PNG
This commit is contained in:
1 parent
65f37a74b8
commit
ab74922b93
5 files changed
+537
-99
No files matched your search
+133
-97
@@ -1,17 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { NewsletterCampaign, NewsletterModule, ProductItem } from '@/types/newsletter';
|
||||
import { NewsletterCampaign, NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
|
||||
import { DEFAULT_CAMPAIGN, SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
|
||||
import { renderEmailHtml } from '@/lib/emailRenderer';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { TopBar } from '@/components/editor/TopBar';
|
||||
import { ConfigPanel } from '@/components/editor/ConfigPanel';
|
||||
import { ProductImportModal } from '@/components/editor/ProductImportModal';
|
||||
import { BadgeManagerModal } from '@/components/editor/BadgeManagerModal';
|
||||
import { BadgeEditorModal } from '@/components/editor/BadgeEditorModal';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ChevronUp, ChevronDown, Copy, Trash2 } from 'lucide-react';
|
||||
import { ChevronUp, ChevronDown, Copy, Trash2, Edit3 } from 'lucide-react';
|
||||
|
||||
export default function Home() {
|
||||
const [campaign, setCampaign] = useState<NewsletterCampaign>(DEFAULT_CAMPAIGN);
|
||||
@@ -22,13 +22,14 @@ export default function Home() {
|
||||
|
||||
// Modály
|
||||
const [isImportOpen, setIsImportOpen] = useState(false);
|
||||
const [isBadgeModalOpen, setIsBadgeModalOpen] = useState(false);
|
||||
const [isBadgeEditorOpen, setIsBadgeEditorOpen] = useState(false);
|
||||
const [targetProductIdForBadge, setTargetProductIdForBadge] = useState<string | null>(null);
|
||||
|
||||
// Načítanie existujúceho draftu z databázy
|
||||
useEffect(() => {
|
||||
async function loadInitial() {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
const { data } = await supabase
|
||||
.from('templates')
|
||||
.select('*')
|
||||
.order('updated_at', { ascending: false })
|
||||
@@ -36,8 +37,7 @@ export default function Home() {
|
||||
|
||||
if (data && data.length > 0) {
|
||||
const dbItem = data[0];
|
||||
// Ak má databáza rovnakú štruktúru, načítame ju, inak necháme DEFAULT_CAMPAIGN 1:1
|
||||
if (dbItem.data && dbItem.data.modules && dbItem.data.modules.length >= 6) {
|
||||
if (dbItem.data && dbItem.data.modules && dbItem.data.modules.length >= 7) {
|
||||
setCampaign({
|
||||
id: dbItem.id,
|
||||
title: dbItem.title,
|
||||
@@ -291,11 +291,36 @@ export default function Home() {
|
||||
}
|
||||
};
|
||||
|
||||
// Kompilácia celého HTML pre responzívny náhľad v reálnom čase
|
||||
const fullHtml = renderEmailHtml(campaign);
|
||||
// Vybraný produkt pre editor cenoviek
|
||||
let targetProduct: ProductItem | null = null;
|
||||
if (targetProductIdForBadge && selectedModule?.data?.products) {
|
||||
targetProduct = selectedModule.data.products.find((p: any) => p.id === targetProductIdForBadge) || null;
|
||||
}
|
||||
|
||||
const handleApplyBadgeToProduct = (badge: BadgeTemplate, renderedPngUrl?: string) => {
|
||||
if (!targetProductIdForBadge || !selectedModule) return;
|
||||
const updatedProducts = (selectedModule.data.products || []).map((p: ProductItem) => {
|
||||
if (p.id === targetProductIdForBadge) {
|
||||
return {
|
||||
...p,
|
||||
badgeId: badge.id,
|
||||
customBadgePngUrl: renderedPngUrl || p.customBadgePngUrl
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
|
||||
handleUpdateModule({
|
||||
...selectedModule,
|
||||
data: {
|
||||
...selectedModule.data,
|
||||
products: updatedProducts
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden bg-[#001a33] font-sans">
|
||||
<div className="flex flex-col h-screen overflow-hidden bg-slate-950 font-sans">
|
||||
{/* 1. Horná lišta */}
|
||||
<TopBar
|
||||
campaign={campaign}
|
||||
@@ -310,89 +335,93 @@ export default function Home() {
|
||||
|
||||
{/* 2. Hlavný pracovný priestor */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Stredné plátno - Živý responzívny e-mail v iframe pre 100% izoláciu CSS a médií */}
|
||||
<main className="flex-1 overflow-y-auto p-4 md:p-8 flex items-start justify-center bg-[#000d1a]">
|
||||
<div className="w-full max-w-[680px] shadow-2xl rounded-2xl overflow-hidden border border-blue-950/50 bg-[#001a33]">
|
||||
{/* Zoznam modulov pre priamu editáciu */}
|
||||
<div className="p-3 bg-[#001a33] space-y-3">
|
||||
{campaign.modules.map((mod, idx) => {
|
||||
const isSelected = mod.id === selectedModuleId;
|
||||
{/* Stredné plátno - Presný originálny gradient a čisté rozloženie */}
|
||||
<main
|
||||
className="flex-1 overflow-y-auto p-4 md:p-8 flex items-start justify-center"
|
||||
style={{
|
||||
backgroundColor: '#001a33',
|
||||
backgroundImage: 'linear-gradient(135deg, #005bb5 0%, #000d1a 100%)'
|
||||
}}
|
||||
>
|
||||
{/* E-mail Container 1:1 k originálu */}
|
||||
<div className="w-full max-w-[600px] my-4">
|
||||
{campaign.modules.map((mod, idx) => {
|
||||
const isSelected = mod.id === selectedModuleId;
|
||||
|
||||
return (
|
||||
return (
|
||||
<div
|
||||
key={mod.id}
|
||||
onClick={() => setSelectedModuleId(mod.id)}
|
||||
className={`relative cursor-pointer transition-all duration-150 ${
|
||||
isSelected
|
||||
? 'outline-2 outline-offset-4 outline-red-500 rounded-2xl shadow-2xl'
|
||||
: 'hover:outline-1 hover:outline-offset-2 hover:outline-white/30 rounded-2xl'
|
||||
}`}
|
||||
>
|
||||
{/* Lišta s nástrojmi pre označený modul */}
|
||||
{isSelected && (
|
||||
<div className="absolute -top-3 right-4 z-40 flex items-center gap-1 bg-[#001a33] text-white px-3 py-1.5 rounded-full text-xs shadow-2xl border border-white/20 backdrop-blur-md">
|
||||
<span className="text-[10px] font-bold text-gray-300 mr-1.5 uppercase tracking-wider">
|
||||
{mod.title}
|
||||
</span>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveModule(idx, 'up');
|
||||
}}
|
||||
disabled={idx === 0}
|
||||
className="hover:text-red-400 disabled:opacity-30 p-1"
|
||||
title="Posunúť hore"
|
||||
>
|
||||
<ChevronUp className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveModule(idx, 'down');
|
||||
}}
|
||||
disabled={idx === campaign.modules.length - 1}
|
||||
className="hover:text-red-400 disabled:opacity-30 p-1"
|
||||
title="Posunúť dole"
|
||||
>
|
||||
<ChevronDown className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateModule(idx);
|
||||
}}
|
||||
className="hover:text-blue-400 p-1 ml-1"
|
||||
title="Duplikovať"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteModule(idx);
|
||||
}}
|
||||
className="hover:text-red-400 p-1 ml-1"
|
||||
title="Zmazať"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vykreslenie modulu bez vnútorných medzier navyše */}
|
||||
<div
|
||||
key={mod.id}
|
||||
onClick={() => setSelectedModuleId(mod.id)}
|
||||
className={`relative group cursor-pointer transition-all duration-200 rounded-2xl border-2 ${
|
||||
isSelected
|
||||
? 'border-red-500 ring-4 ring-red-500/20'
|
||||
: 'border-transparent hover:border-slate-700/60'
|
||||
}`}
|
||||
>
|
||||
{/* Lišta s nástrojmi pre označený modul */}
|
||||
{isSelected && (
|
||||
<div className="absolute top-2 right-2 z-30 flex items-center gap-1.5 bg-slate-900/95 backdrop-blur-md text-white px-2.5 py-1.5 rounded-xl text-xs shadow-2xl border border-slate-700">
|
||||
<span className="text-[10px] font-bold text-gray-400 mr-1 uppercase">
|
||||
{mod.title}
|
||||
</span>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveModule(idx, 'up');
|
||||
}}
|
||||
disabled={idx === 0}
|
||||
className="hover:text-red-400 disabled:opacity-30 p-1"
|
||||
title="Posunúť hore"
|
||||
>
|
||||
<ChevronUp className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveModule(idx, 'down');
|
||||
}}
|
||||
disabled={idx === campaign.modules.length - 1}
|
||||
className="hover:text-red-400 disabled:opacity-30 p-1"
|
||||
title="Posunúť dole"
|
||||
>
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateModule(idx);
|
||||
}}
|
||||
className="hover:text-blue-400 p-1"
|
||||
title="Duplikovať"
|
||||
>
|
||||
<Copy className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteModule(idx);
|
||||
}}
|
||||
className="hover:text-red-400 p-1"
|
||||
title="Zmazať"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vykreslenie modulu 1:1 */}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderEmailHtml({
|
||||
...campaign,
|
||||
modules: [mod]
|
||||
})
|
||||
}}
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderEmailHtml({
|
||||
...campaign,
|
||||
modules: [mod]
|
||||
})
|
||||
}}
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -401,23 +430,30 @@ export default function Home() {
|
||||
<ConfigPanel
|
||||
module={selectedModule}
|
||||
onUpdateModule={handleUpdateModule}
|
||||
onOpenBadgeManager={() => setIsBadgeModalOpen(true)}
|
||||
onOpenBadgeManager={(pId) => {
|
||||
setTargetProductIdForBadge(pId);
|
||||
setIsBadgeEditorOpen(true);
|
||||
}}
|
||||
onOpenImport={() => setIsImportOpen(true)}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{/* Modály */}
|
||||
{/* Modál Hromadného importu */}
|
||||
<ProductImportModal
|
||||
isOpen={isImportOpen}
|
||||
onClose={() => setIsImportOpen(false)}
|
||||
onImportProducts={handleImportProducts}
|
||||
/>
|
||||
|
||||
<BadgeManagerModal
|
||||
isOpen={isBadgeModalOpen}
|
||||
onClose={() => setIsBadgeModalOpen(false)}
|
||||
onSelectBadge={() => {}}
|
||||
{/* Modál Editora cenoviek (SVG -> PNG) */}
|
||||
<BadgeEditorModal
|
||||
isOpen={isBadgeEditorOpen}
|
||||
onClose={() => setIsBadgeEditorOpen(false)}
|
||||
onSelectBadge={handleApplyBadgeToProduct}
|
||||
currentDiscount={targetProduct?.discountText || '25%'}
|
||||
currentPriceMain={targetProduct?.priceMain || '1'}
|
||||
currentPriceSub={targetProduct?.priceSub || '99'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user