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
@@ -12,7 +12,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
const apiKey = process.env.BREVO_API_KEY;
|
||||
const senderName = process.env.BREVO_SENDER_NAME || 'Jandl';
|
||||
const senderName = process.env.BREVO_SENDER_NAME || 'Jakub Hostačný (COOP Newsletter)';
|
||||
const senderEmail = process.env.BREVO_SENDER_EMAIL || 'tomas.rafay@jandl.sk';
|
||||
|
||||
if (!apiKey) {
|
||||
@@ -58,11 +58,12 @@ export async function POST(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Úspech - vrátime campaignId a URL na správu v Brevo
|
||||
// Úspech - vrátime campaignId a priame URL na editáciu a prehľad kampaní v novom Brevo dashboarde
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
campaignId: data.id,
|
||||
brevoUrl: `https://app.brevo.com/camp/detail/email/${data.id}`,
|
||||
brevoUrl: `https://app.brevo.com/campaigns/email/edit/${data.id}`,
|
||||
brevoListingUrl: 'https://app.brevo.com/campaigns/listing',
|
||||
message: 'Kampaň bola úspešne vytvorená v Brevo!'
|
||||
});
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getSupabaseAdmin } from '@/lib/supabase';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const formData = await req.formData();
|
||||
const file = formData.get('file') as File | null;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json({ error: 'Súbor nebol poskytnutý.' }, { status: 400 });
|
||||
}
|
||||
|
||||
const admin = getSupabaseAdmin();
|
||||
const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, '_');
|
||||
const path = `uploads/${Date.now()}-${safeName}`;
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
const { error: uploadError } = await admin.storage
|
||||
.from('newsletter-assets')
|
||||
.upload(path, buffer, {
|
||||
contentType: file.type || 'image/png',
|
||||
upsert: true
|
||||
});
|
||||
|
||||
if (uploadError) {
|
||||
console.error('Upload to Supabase Storage error:', uploadError);
|
||||
return NextResponse.json({ error: uploadError.message }, { status: 500 });
|
||||
}
|
||||
|
||||
const { data: pubData } = admin.storage
|
||||
.from('newsletter-assets')
|
||||
.getPublicUrl(path);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
url: pubData.publicUrl
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error('API /upload error:', err);
|
||||
return NextResponse.json({ error: err.message || 'Chyba servera pri nahrávaní súboru' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
+284
-70
@@ -9,9 +9,10 @@ import { TopBar } from '@/components/editor/TopBar';
|
||||
import { ConfigPanel } from '@/components/editor/ConfigPanel';
|
||||
import { ProductImportModal } from '@/components/editor/ProductImportModal';
|
||||
import { BadgeEditorModal } from '@/components/editor/BadgeEditorModal';
|
||||
import { SavedCampaignsModal } from '@/components/editor/SavedCampaignsModal';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ChevronUp, ChevronDown, Copy, Trash2, Edit3 } from 'lucide-react';
|
||||
import { ChevronUp, ChevronDown, Copy, Trash2, Anchor, Lock } from 'lucide-react';
|
||||
|
||||
export default function Home() {
|
||||
const [campaign, setCampaign] = useState<NewsletterCampaign>(DEFAULT_CAMPAIGN);
|
||||
@@ -23,9 +24,30 @@ export default function Home() {
|
||||
// Modály
|
||||
const [isImportOpen, setIsImportOpen] = useState(false);
|
||||
const [isBadgeEditorOpen, setIsBadgeEditorOpen] = useState(false);
|
||||
const [isSavedCampaignsOpen, setIsSavedCampaignsOpen] = useState(false);
|
||||
const [targetProductIdForBadge, setTargetProductIdForBadge] = useState<string | null>(null);
|
||||
|
||||
// Načítanie existujúceho draftu z databázy
|
||||
// Pomocné funkcie pre fixné ukotvenie hlavičky a pätičky
|
||||
const isTopModule = (type: string) => type === 'header' || type === 'announcement';
|
||||
const isBottomModule = (type: string) => type === 'footer';
|
||||
const isAnchoredModule = (type: string) => isTopModule(type) || isBottomModule(type);
|
||||
|
||||
const enforceAnchors = (mods: NewsletterModule[]): NewsletterModule[] => {
|
||||
const topMods = mods.filter((m) => isTopModule(m.type));
|
||||
const bottomMods = mods.filter((m) => isBottomModule(m.type));
|
||||
const middleMods = mods.filter((m) => !isAnchoredModule(m.type));
|
||||
|
||||
// announcement je pred headerom, ak existujú oba
|
||||
topMods.sort((a, b) => {
|
||||
if (a.type === 'announcement' && b.type === 'header') return -1;
|
||||
if (a.type === 'header' && b.type === 'announcement') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return [...topMods, ...middleMods, ...bottomMods];
|
||||
};
|
||||
|
||||
// Načítanie existujúceho draftu z databázy pri štarte
|
||||
useEffect(() => {
|
||||
async function loadInitial() {
|
||||
try {
|
||||
@@ -44,10 +66,10 @@ export default function Home() {
|
||||
preheader: dbItem.data.preheader || DEFAULT_CAMPAIGN.preheader,
|
||||
status: dbItem.status,
|
||||
brevoCampaignId: dbItem.brevo_campaign_id,
|
||||
modules: dbItem.data.modules
|
||||
modules: enforceAnchors(dbItem.data.modules)
|
||||
});
|
||||
if (dbItem.brevo_campaign_id) {
|
||||
setBrevoUrl(`https://app.brevo.com/camp/detail/email/${dbItem.brevo_campaign_id}`);
|
||||
setBrevoUrl(`https://app.brevo.com/campaigns/email/edit/${dbItem.brevo_campaign_id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +124,7 @@ export default function Home() {
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [campaign.title, campaign.preheader, campaign.modules]);
|
||||
}, [campaign.title, campaign.preheader, campaign.modules, campaign.id]);
|
||||
|
||||
const selectedModule = campaign.modules.find((m) => m.id === selectedModuleId) || null;
|
||||
|
||||
@@ -113,39 +135,56 @@ export default function Home() {
|
||||
}));
|
||||
};
|
||||
|
||||
// Posun modulu: Hlavička a pätička sú ukotvené a bežné moduly sa nemôžu dostať pred hlavičku ani za pätičku
|
||||
const moveModule = (index: number, direction: 'up' | 'down') => {
|
||||
const newModules = [...campaign.modules];
|
||||
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
||||
if (targetIndex < 0 || targetIndex >= newModules.length) return;
|
||||
const currentMod = campaign.modules[index];
|
||||
if (!currentMod || isAnchoredModule(currentMod.type)) return;
|
||||
|
||||
const topCount = campaign.modules.filter((m) => isTopModule(m.type)).length;
|
||||
const footerIndex = campaign.modules.findIndex((m) => isBottomModule(m.type));
|
||||
const maxAllowedIndex = footerIndex !== -1 ? footerIndex - 1 : campaign.modules.length - 1;
|
||||
|
||||
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
||||
if (targetIndex < topCount || targetIndex > maxAllowedIndex) return;
|
||||
|
||||
const newModules = [...campaign.modules];
|
||||
const temp = newModules[index];
|
||||
newModules[index] = newModules[targetIndex];
|
||||
newModules[targetIndex] = temp;
|
||||
|
||||
setCampaign((prev) => ({ ...prev, modules: newModules }));
|
||||
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
|
||||
};
|
||||
|
||||
// Duplikácia: Hlavička a pätička sa nedajú duplikovať
|
||||
const duplicateModule = (index: number) => {
|
||||
const original = campaign.modules[index];
|
||||
if (isAnchoredModule(original.type)) return;
|
||||
|
||||
const clone: NewsletterModule = {
|
||||
...original,
|
||||
id: `mod-${Date.now()}`,
|
||||
title: `${original.title} (kópia)`,
|
||||
data: JSON.parse(JSON.stringify(original.data))
|
||||
};
|
||||
|
||||
const newModules = [...campaign.modules];
|
||||
newModules.splice(index + 1, 0, clone);
|
||||
setCampaign((prev) => ({ ...prev, modules: newModules }));
|
||||
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
|
||||
setSelectedModuleId(clone.id);
|
||||
};
|
||||
|
||||
// Zmazanie: Hlavička a pätička sa nedajú vymazať
|
||||
const deleteModule = (index: number) => {
|
||||
const mod = campaign.modules[index];
|
||||
if (isAnchoredModule(mod.type)) return;
|
||||
if (campaign.modules.length <= 1) return;
|
||||
|
||||
const newModules = campaign.modules.filter((_, i) => i !== index);
|
||||
setCampaign((prev) => ({ ...prev, modules: newModules }));
|
||||
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
|
||||
setSelectedModuleId(newModules[0]?.id || null);
|
||||
};
|
||||
|
||||
// Pridanie modulu: Vloží sa vždy PRED pätičku (pätička ostáva pevne na spodku)
|
||||
const handleAddModule = (type: any) => {
|
||||
let newMod: NewsletterModule;
|
||||
if (type === 'banner') {
|
||||
@@ -196,10 +235,16 @@ export default function Home() {
|
||||
};
|
||||
}
|
||||
|
||||
setCampaign((prev) => ({
|
||||
...prev,
|
||||
modules: [...prev.modules, newMod]
|
||||
}));
|
||||
setCampaign((prev) => {
|
||||
const footerIdx = prev.modules.findIndex((m) => isBottomModule(m.type));
|
||||
const newModules = [...prev.modules];
|
||||
if (footerIdx !== -1) {
|
||||
newModules.splice(footerIdx, 0, newMod);
|
||||
} else {
|
||||
newModules.push(newMod);
|
||||
}
|
||||
return { ...prev, modules: enforceAnchors(newModules) };
|
||||
});
|
||||
setSelectedModuleId(newMod.id);
|
||||
};
|
||||
|
||||
@@ -221,6 +266,89 @@ export default function Home() {
|
||||
handleUpdateModule(updated);
|
||||
};
|
||||
|
||||
// Manuálne uloženie kampane
|
||||
const handleManualSave = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
if (!campaign.id) {
|
||||
const { data, error } = await supabase
|
||||
.from('templates')
|
||||
.insert([
|
||||
{
|
||||
title: campaign.title,
|
||||
status: campaign.status,
|
||||
data: {
|
||||
preheader: campaign.preheader,
|
||||
modules: campaign.modules
|
||||
}
|
||||
}
|
||||
])
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
if (data && data.length > 0) {
|
||||
setCampaign((prev) => ({ ...prev, id: data[0].id }));
|
||||
}
|
||||
} else {
|
||||
const { error } = await supabase
|
||||
.from('templates')
|
||||
.update({
|
||||
title: campaign.title,
|
||||
data: {
|
||||
preheader: campaign.preheader,
|
||||
modules: campaign.modules
|
||||
},
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', campaign.id);
|
||||
|
||||
if (error) throw error;
|
||||
}
|
||||
alert('Kampaň bola úspešne uložená do databázy!');
|
||||
} catch (err: any) {
|
||||
alert(`Chyba pri ukladaní: ${err.message}`);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Duplikácia aktuálnej kampane v Supabase
|
||||
const handleDuplicateCurrentCampaign = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const newTitle = `${campaign.title} (kópia)`;
|
||||
const { data, error } = await supabase
|
||||
.from('templates')
|
||||
.insert([
|
||||
{
|
||||
title: newTitle,
|
||||
status: 'draft',
|
||||
data: {
|
||||
preheader: campaign.preheader,
|
||||
modules: campaign.modules
|
||||
}
|
||||
}
|
||||
])
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
if (data && data.length > 0) {
|
||||
setCampaign({
|
||||
id: data[0].id,
|
||||
title: newTitle,
|
||||
preheader: campaign.preheader,
|
||||
status: 'draft',
|
||||
modules: campaign.modules
|
||||
});
|
||||
alert(`Kampaň bola zduplikovaná ako "${newTitle}"!`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
alert(`Chyba pri duplikovaní: ${err.message}`);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportZip = async () => {
|
||||
const rawHtml = renderEmailHtml(campaign);
|
||||
let finalHtml = rawHtml;
|
||||
@@ -319,17 +447,24 @@ export default function Home() {
|
||||
});
|
||||
};
|
||||
|
||||
const topCount = campaign.modules.filter((m) => isTopModule(m.type)).length;
|
||||
const footerIdx = campaign.modules.findIndex((m) => isBottomModule(m.type));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden bg-slate-950 font-sans">
|
||||
{/* 1. Horná lišta */}
|
||||
<TopBar
|
||||
campaign={campaign}
|
||||
isSaving={isSaving}
|
||||
onSave={() => {}}
|
||||
onSave={handleManualSave}
|
||||
onUpdateTitle={(title) => setCampaign((prev) => ({ ...prev, title }))}
|
||||
onOpenSavedCampaigns={() => setIsSavedCampaignsOpen(true)}
|
||||
onDuplicateCampaign={handleDuplicateCurrentCampaign}
|
||||
onExportZip={handleExportZip}
|
||||
onExportBrevo={handleExportBrevo}
|
||||
isExportingBrevo={isExportingBrevo}
|
||||
brevoCampaignUrl={brevoUrl}
|
||||
brevoListingUrl="https://app.brevo.com/campaigns/listing"
|
||||
onAddModule={handleAddModule}
|
||||
/>
|
||||
|
||||
@@ -347,75 +482,112 @@ export default function Home() {
|
||||
<div className="w-full max-w-[600px] my-4">
|
||||
{campaign.modules.map((mod, idx) => {
|
||||
const isSelected = mod.id === selectedModuleId;
|
||||
const isAnchored = isAnchoredModule(mod.type);
|
||||
const isTop = isTopModule(mod.type);
|
||||
const isBottom = isBottomModule(mod.type);
|
||||
|
||||
const canMoveUp = !isAnchored && idx > topCount;
|
||||
const canMoveDown = !isAnchored && idx < (footerIdx !== -1 ? footerIdx - 1 : campaign.modules.length - 1);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={mod.id}
|
||||
onClick={() => setSelectedModuleId(mod.id)}
|
||||
className={`relative cursor-pointer transition-all duration-150 ${
|
||||
className={`relative cursor-pointer transition-all duration-150 rounded-2xl ${
|
||||
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'
|
||||
? 'ring-2 ring-offset-4 ring-offset-[#001a33] ring-red-500 shadow-2xl'
|
||||
: 'hover:ring-1 hover:ring-offset-2 hover:ring-offset-[#001a33] hover:ring-white/30'
|
||||
}`}
|
||||
>
|
||||
{/* 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">
|
||||
<div className="absolute -top-3 right-4 z-40 flex items-center gap-1.5 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 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>
|
||||
|
||||
{/* Ak je modul ukotvený (Hlavička / Pätička), nezobrazujeme tlačidlá na presun a zmazanie */}
|
||||
{isAnchored ? (
|
||||
<span className="text-[9px] font-bold bg-white/10 text-amber-300 px-2 py-0.5 rounded-full border border-white/10 uppercase flex items-center gap-1">
|
||||
<Lock className="w-2.5 h-2.5" />
|
||||
{isBottom ? 'Ukotvená pätička' : 'Ukotvená hlavička'}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveModule(idx, 'up');
|
||||
}}
|
||||
disabled={!canMoveUp}
|
||||
className="hover:text-red-400 disabled:opacity-30 p-1"
|
||||
title={canMoveUp ? 'Posunúť vyššie' : 'Nemožno posunúť nad hlavičku'}
|
||||
>
|
||||
<ChevronUp className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveModule(idx, 'down');
|
||||
}}
|
||||
disabled={!canMoveDown}
|
||||
className="hover:text-red-400 disabled:opacity-30 p-1"
|
||||
title={canMoveDown ? 'Posunúť nižšie' : 'Nemožno posunúť pod pätičku'}
|
||||
>
|
||||
<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ť sekciu"
|
||||
>
|
||||
<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ť sekciu"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vykreslenie čistého modulu bez obaľovacieho gradientu na každom bloku */}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderSingleModuleHtml(mod, campaign)
|
||||
}}
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
{/* Vykreslenie modulu: ak je loyalty_club vypnutý, zobrazíme prívetivý placeholder */}
|
||||
{mod.type === 'loyalty_club' && mod.data.enabled === false ? (
|
||||
<div className="p-8 my-4 border-2 border-dashed border-red-500/40 bg-red-950/30 rounded-2xl text-center">
|
||||
<p className="text-xs font-bold text-red-300 uppercase tracking-wider">
|
||||
Sekcia Vernostné zľavy (Vypnutá)
|
||||
</p>
|
||||
<p className="text-[11px] text-gray-300 mt-1">
|
||||
Táto sekcia je v odosielanom e-maile skrytá. Kliknutím sem ju môžete aktivovať.
|
||||
</p>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleUpdateModule({ ...mod, data: { ...mod.data, enabled: true } });
|
||||
}}
|
||||
className="mt-3 px-4 py-1.5 bg-red-600 hover:bg-red-700 text-white text-xs font-bold rounded-lg transition-colors shadow-sm"
|
||||
>
|
||||
Aktivovať vernostné zľavy
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderSingleModuleHtml(mod, campaign)
|
||||
}}
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -453,6 +625,48 @@ export default function Home() {
|
||||
currentPriceSub={targetProduct?.priceSub || '99'}
|
||||
currentUnitPrice={targetProduct?.unitPrice || '1kg = 4,91 €'}
|
||||
/>
|
||||
|
||||
{/* Modál Moje newslettre (Ukladanie, duplikácia, vytváranie a prepínanie) */}
|
||||
<SavedCampaignsModal
|
||||
isOpen={isSavedCampaignsOpen}
|
||||
onClose={() => setIsSavedCampaignsOpen(false)}
|
||||
currentCampaign={campaign}
|
||||
onLoadCampaign={(selected: NewsletterCampaign) => {
|
||||
setCampaign({ ...selected, modules: enforceAnchors(selected.modules) });
|
||||
setSelectedModuleId(selected.modules[1]?.id || selected.modules[0]?.id || null);
|
||||
if (selected.brevoCampaignId) {
|
||||
setBrevoUrl(`https://app.brevo.com/campaigns/email/edit/${selected.brevoCampaignId}`);
|
||||
} else {
|
||||
setBrevoUrl(null);
|
||||
}
|
||||
}}
|
||||
onSaveCurrentAsCopy={async (newTitle: string) => {
|
||||
const { data, error } = await supabase
|
||||
.from('templates')
|
||||
.insert([
|
||||
{
|
||||
title: newTitle,
|
||||
status: 'draft',
|
||||
data: {
|
||||
preheader: campaign.preheader,
|
||||
modules: campaign.modules
|
||||
}
|
||||
}
|
||||
])
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
if (data && data[0]) {
|
||||
setCampaign({
|
||||
id: data[0].id,
|
||||
title: newTitle,
|
||||
preheader: campaign.preheader,
|
||||
status: 'draft',
|
||||
modules: campaign.modules
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user