Add Pomelli-inspired Brand Overview to Zona de marca.
Light Centro de Mando layout with logo, fonts, colors, tagline, values, tone and completeness — edits still live in Detalles del kit. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
413
src/components/BrandOverview.tsx
Normal file
413
src/components/BrandOverview.tsx
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
CheckCircle2,
|
||||||
|
ExternalLink,
|
||||||
|
ImageIcon,
|
||||||
|
Palette,
|
||||||
|
Type,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Surface } from '@/components/ui';
|
||||||
|
import {
|
||||||
|
primaryLogo,
|
||||||
|
type Brandbook,
|
||||||
|
type BrandbookSectionId,
|
||||||
|
} from '@/lib/brandbook';
|
||||||
|
|
||||||
|
function LogoPreview({
|
||||||
|
assetFileId,
|
||||||
|
alt,
|
||||||
|
}: {
|
||||||
|
assetFileId: string;
|
||||||
|
alt: string;
|
||||||
|
}) {
|
||||||
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
(async () => {
|
||||||
|
const r = await fetch(
|
||||||
|
`/api/assets/download?id=${encodeURIComponent(assetFileId)}`
|
||||||
|
);
|
||||||
|
const j = await r.json();
|
||||||
|
if (!cancelled && r.ok && j.url) setUrl(j.url);
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [assetFileId]);
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-16 w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-gray-300 bg-gray-50 text-xs text-gray-400">
|
||||||
|
Cargando logo…
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
|
<img
|
||||||
|
src={url}
|
||||||
|
alt={alt}
|
||||||
|
className="max-h-16 w-auto max-w-full object-contain"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PillRow({
|
||||||
|
title,
|
||||||
|
items,
|
||||||
|
emptyHint,
|
||||||
|
accent = 'brand',
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
items: string[];
|
||||||
|
emptyHint: string;
|
||||||
|
accent?: 'brand' | 'sky' | 'violet' | 'amber';
|
||||||
|
}) {
|
||||||
|
const tone =
|
||||||
|
accent === 'sky'
|
||||||
|
? 'border-sky-200 bg-sky-50 text-sky-900'
|
||||||
|
: accent === 'violet'
|
||||||
|
? 'border-violet-200 bg-violet-50 text-violet-900'
|
||||||
|
: accent === 'amber'
|
||||||
|
? 'border-amber-200 bg-amber-50 text-amber-900'
|
||||||
|
: 'border-brand-200 bg-brand-50 text-brand-900';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
{items.length ? (
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{items.map((item) => (
|
||||||
|
<span
|
||||||
|
key={item}
|
||||||
|
className={`inline-flex rounded-full border px-3 py-1 text-xs font-semibold ${tone}`}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-gray-400 italic">{emptyHint}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(/^\/+/, '')}`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BrandOverview({
|
||||||
|
book,
|
||||||
|
tenantLabel,
|
||||||
|
score,
|
||||||
|
missing,
|
||||||
|
onEditSection,
|
||||||
|
onContinueEdit,
|
||||||
|
}: {
|
||||||
|
book: Brandbook;
|
||||||
|
tenantLabel: string;
|
||||||
|
score: number;
|
||||||
|
missing: string[];
|
||||||
|
onEditSection: (id: BrandbookSectionId) => void;
|
||||||
|
onContinueEdit: () => void;
|
||||||
|
}) {
|
||||||
|
const logo = primaryLogo(book);
|
||||||
|
const site = websiteFromBook(book);
|
||||||
|
const fonts = book.fonts_kit.slice(0, 4);
|
||||||
|
const colors = book.colors.slice(0, 8);
|
||||||
|
const values = book.values.filter(Boolean).slice(0, 8);
|
||||||
|
const aesthetic = [
|
||||||
|
...book.personality,
|
||||||
|
book.photography_style,
|
||||||
|
book.illustration_style,
|
||||||
|
book.visual_notes,
|
||||||
|
]
|
||||||
|
.flatMap((x) =>
|
||||||
|
String(x || '')
|
||||||
|
.split(/[,;·|/]/)
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
)
|
||||||
|
.filter((v, i, arr) => arr.indexOf(v) === i)
|
||||||
|
.slice(0, 8);
|
||||||
|
const tonePills = [
|
||||||
|
book.tone,
|
||||||
|
...book.voice_do.slice(0, 3),
|
||||||
|
...book.personality.slice(0, 2),
|
||||||
|
]
|
||||||
|
.map((s) => String(s || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((v, i, arr) => arr.indexOf(v) === i)
|
||||||
|
.slice(0, 6);
|
||||||
|
|
||||||
|
const overviewText = useMemo(() => {
|
||||||
|
return (
|
||||||
|
book.elevator_pitch ||
|
||||||
|
book.brand_promise ||
|
||||||
|
book.positioning ||
|
||||||
|
book.mission ||
|
||||||
|
''
|
||||||
|
).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>
|
||||||
|
|
||||||
|
<div className="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">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500">
|
||||||
|
Identidad
|
||||||
|
</p>
|
||||||
|
<h3 className="text-2xl font-extrabold text-gray-900 tracking-tight">
|
||||||
|
{book.brand_name || tenantLabel}
|
||||||
|
</h3>
|
||||||
|
{site ? (
|
||||||
|
<a
|
||||||
|
href={site}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="inline-flex items-center gap-1 text-sm font-medium text-brand-700 hover:underline"
|
||||||
|
>
|
||||||
|
{site.replace(/^https?:\/\//i, '')}
|
||||||
|
<ExternalLink className="h-3.5 w-3.5" />
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onEditSection('canales')}
|
||||||
|
className="text-sm text-gray-400 hover:text-brand-700"
|
||||||
|
>
|
||||||
|
Añadir web en Canales →
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<div className="mt-4 flex min-h-[88px] items-center justify-center rounded-2xl border border-gray-200 bg-white px-6 py-5 shadow-sm">
|
||||||
|
{logo ? (
|
||||||
|
<LogoPreview
|
||||||
|
assetFileId={logo.asset_file_id}
|
||||||
|
alt={logo.label || book.brand_name || 'Logo'}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onEditSection('visual')}
|
||||||
|
className="flex flex-col items-center gap-2 text-gray-400 hover:text-brand-700"
|
||||||
|
>
|
||||||
|
<ImageIcon className="h-8 w-8" />
|
||||||
|
<span className="text-xs font-semibold">Subir logo</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500 inline-flex items-center gap-1.5">
|
||||||
|
<Type className="h-3.5 w-3.5" /> Tipografías
|
||||||
|
</p>
|
||||||
|
{fonts.length ? (
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{fonts.map((f) => (
|
||||||
|
<div
|
||||||
|
key={f.id}
|
||||||
|
className="rounded-xl border border-gray-200 bg-gray-50 px-3 py-3"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="text-2xl font-semibold text-gray-900 leading-none"
|
||||||
|
style={{ fontFamily: `"${f.family}", sans-serif` }}
|
||||||
|
>
|
||||||
|
Aa
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-[11px] font-semibold text-gray-700 truncate">
|
||||||
|
{f.family}
|
||||||
|
</p>
|
||||||
|
<p className="text-[10px] text-gray-500 capitalize">
|
||||||
|
{f.role}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onEditSection('visual')}
|
||||||
|
className="w-full rounded-xl border border-dashed border-gray-300 bg-gray-50 px-3 py-6 text-xs text-gray-500 hover:border-brand-300 hover:text-brand-700"
|
||||||
|
>
|
||||||
|
Añadir tipografías en Visual
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500 inline-flex items-center gap-1.5">
|
||||||
|
<Palette className="h-3.5 w-3.5" /> Colores
|
||||||
|
</p>
|
||||||
|
{colors.length ? (
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{colors.map((c) => (
|
||||||
|
<div key={c.id} className="flex flex-col items-center gap-1.5">
|
||||||
|
<span
|
||||||
|
className="h-11 w-11 rounded-full border border-black/10 shadow-sm"
|
||||||
|
style={{ background: c.hex }}
|
||||||
|
title={`${c.name} ${c.hex}`}
|
||||||
|
/>
|
||||||
|
<span className="text-[10px] font-mono text-gray-600">
|
||||||
|
{c.hex}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onEditSection('visual')}
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Definir paleta en Visual
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Tagline */}
|
||||||
|
<section className="rounded-2xl border border-gray-200 bg-gradient-to-r from-brand-50/80 via-white to-sky-50/60 px-5 py-6">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500">
|
||||||
|
Tagline
|
||||||
|
</p>
|
||||||
|
{book.tagline ? (
|
||||||
|
<p className="mt-2 text-xl sm:text-2xl font-semibold text-brand-800 leading-snug">
|
||||||
|
{book.tagline}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onEditSection('identidad')}
|
||||||
|
className="mt-2 text-sm text-gray-400 italic hover:text-brand-700"
|
||||||
|
>
|
||||||
|
Añadir tagline en Identidad →
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="grid gap-6 lg:grid-cols-3">
|
||||||
|
<PillRow
|
||||||
|
title="Valores de marca"
|
||||||
|
items={values}
|
||||||
|
emptyHint="Completa valores en Identidad"
|
||||||
|
accent="brand"
|
||||||
|
/>
|
||||||
|
<PillRow
|
||||||
|
title="Estética / personalidad"
|
||||||
|
items={aesthetic}
|
||||||
|
emptyHint="Personalidad + notas visuales"
|
||||||
|
accent="violet"
|
||||||
|
/>
|
||||||
|
<PillRow
|
||||||
|
title="Tono de voz"
|
||||||
|
items={tonePills}
|
||||||
|
emptyHint="Define tono en Voz y tono"
|
||||||
|
accent="sky"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Business overview */}
|
||||||
|
<section className="space-y-2">
|
||||||
|
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-gray-500">
|
||||||
|
Business overview
|
||||||
|
</p>
|
||||||
|
<div className="rounded-2xl border border-gray-200 bg-white px-4 py-4 text-sm leading-relaxed text-gray-700">
|
||||||
|
{overviewText ? (
|
||||||
|
overviewText
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onEditSection('identidad')}
|
||||||
|
className="text-gray-400 italic hover:text-brand-700"
|
||||||
|
>
|
||||||
|
Escribe el elevator pitch o la promesa de marca →
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</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">
|
||||||
|
<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">
|
||||||
|
{score}% completo
|
||||||
|
</p>
|
||||||
|
{score >= 100 ? (
|
||||||
|
<span className="inline-flex items-center gap-1 text-[11px] font-semibold text-emerald-700">
|
||||||
|
<CheckCircle2 className="h-3.5 w-3.5" /> Kit sólido
|
||||||
|
</span>
|
||||||
|
) : missing[0] ? (
|
||||||
|
<p className="truncate text-[11px] text-amber-700">
|
||||||
|
Falta: {missing.slice(0, 2).join(' · ')}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 h-2 overflow-hidden rounded-full bg-gray-200">
|
||||||
|
<div
|
||||||
|
className="h-full rounded-full bg-brand-500 transition-all"
|
||||||
|
style={{ width: `${Math.min(100, Math.max(0, score))}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onContinueEdit}
|
||||||
|
className="inline-flex shrink-0 items-center justify-center gap-2 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white hover:bg-brand-700"
|
||||||
|
>
|
||||||
|
{score >= 80 ? 'Seguir editando' : 'Completar marca'}
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Surface>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
Type,
|
Type,
|
||||||
Palette,
|
Palette,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { PageHeader, Surface, SectionTitle } from '@/components/ui';
|
import { PageHeader, Surface, SectionTitle, SegmentedControl } from '@/components/ui';
|
||||||
import {
|
import {
|
||||||
ASSET_KIND_LABELS,
|
ASSET_KIND_LABELS,
|
||||||
BRANDBOOK_SECTIONS,
|
BRANDBOOK_SECTIONS,
|
||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
type FontToken,
|
type FontToken,
|
||||||
} from '@/lib/brandbook';
|
} from '@/lib/brandbook';
|
||||||
import GoogleFontsGallery from '@/components/GoogleFontsGallery';
|
import GoogleFontsGallery from '@/components/GoogleFontsGallery';
|
||||||
|
import BrandOverview from '@/components/BrandOverview';
|
||||||
|
|
||||||
function Field({
|
function Field({
|
||||||
label,
|
label,
|
||||||
@@ -94,6 +95,7 @@ function KitThumb({ assetFileId, alt }: { assetFileId: string; alt: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||||
|
const [viewMode, setViewMode] = useState<'overview' | 'edit'>('overview');
|
||||||
const [section, setSection] = useState<BrandbookSectionId>('identidad');
|
const [section, setSection] = useState<BrandbookSectionId>('identidad');
|
||||||
const [book, setBook] = useState<Brandbook>(() => emptyBrandbook(tenantLabel));
|
const [book, setBook] = useState<Brandbook>(() => emptyBrandbook(tenantLabel));
|
||||||
const [slug, setSlug] = useState('');
|
const [slug, setSlug] = useState('');
|
||||||
@@ -311,7 +313,7 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
eyebrow="Marca"
|
eyebrow="Marca"
|
||||||
title="Zona de marca"
|
title="Zona de marca"
|
||||||
description={`${tenantLabel}${slug ? ` · ${slug}` : ''} · Brand kit completo para ads, calendarios, LinkedIn y blog.`}
|
description={`${tenantLabel}${slug ? ` · ${slug}` : ''} · Brand Overview + kit editable para ads, calendarios, LinkedIn y blog.`}
|
||||||
actions={
|
actions={
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -344,6 +346,42 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SegmentedControl
|
||||||
|
value={viewMode}
|
||||||
|
onChange={setViewMode}
|
||||||
|
options={[
|
||||||
|
{ id: 'overview', label: 'Brand Overview' },
|
||||||
|
{ id: 'edit', label: 'Detalles del kit' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{err ? (
|
||||||
|
<p className="text-sm text-red-600 bg-red-50 border border-red-100 rounded-lg px-3 py-2">
|
||||||
|
{err}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
{okMsg ? (
|
||||||
|
<p className="text-sm text-emerald-700 bg-emerald-50 border border-emerald-100 rounded-lg px-3 py-2">
|
||||||
|
{okMsg}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{viewMode === 'overview' ? (
|
||||||
|
<BrandOverview
|
||||||
|
book={book}
|
||||||
|
tenantLabel={tenantLabel}
|
||||||
|
score={score}
|
||||||
|
missing={missing}
|
||||||
|
onContinueEdit={() => setViewMode('edit')}
|
||||||
|
onEditSection={(id) => {
|
||||||
|
setSection(id);
|
||||||
|
setViewMode('edit');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{viewMode === 'edit' ? (
|
||||||
|
<>
|
||||||
{/* Identity strip */}
|
{/* Identity strip */}
|
||||||
<Surface className="!p-4">
|
<Surface className="!p-4">
|
||||||
<div className="flex flex-col sm:flex-row gap-4 items-start">
|
<div className="flex flex-col sm:flex-row gap-4 items-start">
|
||||||
@@ -485,17 +523,6 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
|||||||
</div>
|
</div>
|
||||||
</Surface>
|
</Surface>
|
||||||
|
|
||||||
{err ? (
|
|
||||||
<p className="text-sm text-red-600 bg-red-50 border border-red-100 rounded-lg px-3 py-2">
|
|
||||||
{err}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{okMsg ? (
|
|
||||||
<p className="text-sm text-emerald-700 bg-emerald-50 border border-emerald-100 rounded-lg px-3 py-2">
|
|
||||||
{okMsg}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row gap-4">
|
<div className="flex flex-col lg:flex-row gap-4">
|
||||||
<nav className="lg:w-56 shrink-0 flex lg:flex-col gap-1 overflow-x-auto pb-1">
|
<nav className="lg:w-56 shrink-0 flex lg:flex-col gap-1 overflow-x-auto pb-1">
|
||||||
{BRANDBOOK_SECTIONS.map((s) => (
|
{BRANDBOOK_SECTIONS.map((s) => (
|
||||||
@@ -1696,6 +1723,8 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
|||||||
)}
|
)}
|
||||||
</Surface>
|
</Surface>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user