feat: Zona de marca tipo Estilo/Brief + brandbooks desde ads/RRSS
UI con nav lateral numerada y panel editable; parse de brandbook JSON string; enrich desde Meta/Google/IG/FB/web/CSS para que app.4ventures.es muestre el kit. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -99,16 +99,18 @@ function PillRow({
|
||||
}
|
||||
|
||||
function websiteFromBook(book: Brandbook): string | null {
|
||||
const fromChannels =
|
||||
book.channel_handles?.blog ||
|
||||
book.channels?.blog ||
|
||||
book.channels?.instagram ||
|
||||
'';
|
||||
const raw = String(fromChannels || '').trim();
|
||||
if (!raw) return null;
|
||||
if (/^https?:\/\//i.test(raw)) return raw;
|
||||
if (raw.includes('.') && !raw.startsWith('@')) {
|
||||
return `https://${raw.replace(/^\/+/, '')}`;
|
||||
const candidates = [
|
||||
book.website,
|
||||
book.channel_handles?.blog,
|
||||
book.channels?.blog,
|
||||
];
|
||||
for (const c of candidates) {
|
||||
const raw = String(c || '').trim();
|
||||
if (!raw) continue;
|
||||
if (/^https?:\/\//i.test(raw)) return raw;
|
||||
if (raw.includes('.') && !raw.startsWith('@')) {
|
||||
return `https://${raw.replace(/^\/+/, '')}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -120,6 +122,7 @@ export default function BrandOverview({
|
||||
missing,
|
||||
onEditSection,
|
||||
onContinueEdit,
|
||||
embedded = false,
|
||||
}: {
|
||||
book: Brandbook;
|
||||
tenantLabel: string;
|
||||
@@ -127,6 +130,8 @@ export default function BrandOverview({
|
||||
missing: string[];
|
||||
onEditSection: (id: BrandbookSectionId) => void;
|
||||
onContinueEdit: () => void;
|
||||
/** Cuando va dentro del panel de Zona de marca (sin card exterior). */
|
||||
embedded?: boolean;
|
||||
}) {
|
||||
const logo = primaryLogo(book);
|
||||
const site = websiteFromBook(book);
|
||||
@@ -167,34 +172,44 @@ export default function BrandOverview({
|
||||
).trim();
|
||||
}, [book]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Surface className="!p-0 overflow-hidden border-gray-200">
|
||||
<div className="border-b border-gray-200 bg-gradient-to-br from-gray-50 via-white to-brand-50/40 px-5 py-4 sm:px-6">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.18em] text-brand-600">
|
||||
Brand Overview
|
||||
</p>
|
||||
<h2 className="mt-1 text-xl font-extrabold tracking-tight text-gray-900 sm:text-2xl">
|
||||
Perfil de marca
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
Resumen visual del brandbook · editable en Detalles del kit
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onContinueEdit}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-semibold text-gray-800 hover:bg-gray-50"
|
||||
>
|
||||
Editar brand kit
|
||||
<ArrowRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
const header = (
|
||||
<div
|
||||
className={
|
||||
embedded
|
||||
? 'flex flex-wrap items-start justify-between gap-3 pb-2'
|
||||
: 'border-b border-gray-200 bg-gradient-to-br from-gray-50 via-white to-brand-50/40 px-5 py-4 sm:px-6'
|
||||
}
|
||||
>
|
||||
<div className={embedded ? '' : undefined}>
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.18em] text-brand-600">
|
||||
Brand Overview
|
||||
</p>
|
||||
<h2
|
||||
className={`mt-1 font-extrabold tracking-tight text-gray-900 ${
|
||||
embedded ? 'text-base' : 'text-xl sm:text-2xl'
|
||||
}`}
|
||||
>
|
||||
Perfil de marca
|
||||
</h2>
|
||||
{!embedded ? (
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
Resumen visual del brandbook · editable por secciones
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onContinueEdit}
|
||||
className="inline-flex items-center gap-1.5 rounded-xl border border-gray-200 bg-white px-3 py-2 text-xs font-bold text-gray-800 hover:bg-gray-50"
|
||||
>
|
||||
Editar identidad
|
||||
<ArrowRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className="space-y-8 px-5 py-6 sm:px-6">
|
||||
const content = (
|
||||
<div className={embedded ? 'space-y-8' : 'space-y-8 px-5 py-6 sm:px-6'}>
|
||||
{/* Identity */}
|
||||
<section className="grid gap-6 lg:grid-cols-[1.2fr_1fr]">
|
||||
<div className="space-y-3">
|
||||
@@ -372,10 +387,70 @@ export default function BrandOverview({
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Footer progress */}
|
||||
<div className="flex flex-col gap-3 border-t border-gray-200 bg-gray-50 px-5 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
|
||||
{/* Copy real ads / RRSS */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500">
|
||||
Copy real (Meta / Google / RRSS)
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onEditSection('ads')}
|
||||
className="text-[11px] font-semibold text-brand-700 hover:underline"
|
||||
>
|
||||
Ver en Ads →
|
||||
</button>
|
||||
</div>
|
||||
{(book.winning_hooks || []).length ||
|
||||
(book.ads_usps || []).length ||
|
||||
(book.ads_ctas || []).length ? (
|
||||
<div className="space-y-3 rounded-2xl border border-amber-200 bg-amber-50/40 px-4 py-4">
|
||||
{(book.ads_ctas || []).length ? (
|
||||
<p className="text-xs text-gray-700">
|
||||
<span className="font-semibold">CTAs:</span>{' '}
|
||||
{(book.ads_ctas || []).slice(0, 6).join(' · ')}
|
||||
</p>
|
||||
) : null}
|
||||
{(book.ads_usps || []).length ? (
|
||||
<PillRow
|
||||
title="USPs en anuncios"
|
||||
items={(book.ads_usps || []).slice(0, 6)}
|
||||
emptyHint=""
|
||||
accent="amber"
|
||||
/>
|
||||
) : null}
|
||||
{(book.winning_hooks || []).length ? (
|
||||
<ul className="space-y-1.5 text-xs text-gray-700 list-disc pl-4">
|
||||
{(book.winning_hooks || []).slice(0, 5).map((h) => (
|
||||
<li key={h.slice(0, 40)} className="leading-snug">
|
||||
{h}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onEditSection('ads')}
|
||||
className="w-full rounded-xl border border-dashed border-gray-300 bg-gray-50 px-3 py-4 text-xs text-gray-500 hover:border-brand-300"
|
||||
>
|
||||
Sin copy de ads aún · Auto-rellenar marca o conectar Meta/Google
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div
|
||||
className={`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between ${
|
||||
embedded
|
||||
? 'border-t border-gray-100 pt-4'
|
||||
: 'border-t border-gray-200 bg-gray-50 px-5 py-4 sm:px-6'
|
||||
}`}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-xs font-semibold text-gray-800">
|
||||
@@ -407,6 +482,24 @@ export default function BrandOverview({
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (embedded) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{header}
|
||||
{content}
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Surface className="!p-0 overflow-hidden border-gray-200">
|
||||
{header}
|
||||
{content}
|
||||
{footer}
|
||||
</Surface>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user