fix: tema claro blanco, logo 4Ventures y menú original
Fondo blanco en la app; menú con labels/iconos originales; logo en sidebar y login. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,7 +33,7 @@ export default async function AdTrackerPage() {
|
||||
|
||||
if (ctx.portfolioWide) {
|
||||
return (
|
||||
<p className="text-sm text-gray-500 border border-gray-800 rounded-xl p-6 bg-[#111827]">
|
||||
<p className="text-sm text-gray-500 border border-gray-200 rounded-xl p-6 bg-white">
|
||||
Selecciona un cliente en Contexto para abrir su Ad Tracker (privado por
|
||||
tenant).
|
||||
</p>
|
||||
|
||||
@@ -82,41 +82,41 @@ export default async function AgencyPage() {
|
||||
|
||||
{/* Global Agency KPI Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-xs text-gray-400 font-medium">Clientes / Tenants</span>
|
||||
<Building2 className="w-4 h-4 text-brand-400" />
|
||||
</div>
|
||||
<p className="text-2xl font-extrabold text-white">{tenants.length}</p>
|
||||
<p className="text-2xl font-extrabold text-gray-900">{tenants.length}</p>
|
||||
<p className="text-[11px] text-emerald-400 mt-1">100% aislados vía RLS</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-xs text-gray-400 font-medium">Inversión Gestionada</span>
|
||||
<DollarSign className="w-4 h-4 text-blue-400" />
|
||||
</div>
|
||||
<p className="text-2xl font-extrabold text-white">
|
||||
<p className="text-2xl font-extrabold text-gray-900">
|
||||
{totalSpend.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €
|
||||
</p>
|
||||
<p className="text-[11px] text-gray-400 mt-1">Vía Windsor.ai pipeline</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-xs text-gray-400 font-medium">Leads Captados</span>
|
||||
<Users className="w-4 h-4 text-purple-400" />
|
||||
</div>
|
||||
<p className="text-2xl font-extrabold text-white">{totalLeads}</p>
|
||||
<p className="text-2xl font-extrabold text-gray-900">{totalLeads}</p>
|
||||
<p className="text-[11px] text-gray-400 mt-1">{totalWonDeals} cerrados ganados</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-xs text-gray-400 font-medium">ROAS Blended Agencia</span>
|
||||
<TrendingUp className="w-4 h-4 text-emerald-400" />
|
||||
</div>
|
||||
<p className="text-2xl font-extrabold text-white">{blendedRoas}x</p>
|
||||
<p className="text-2xl font-extrabold text-gray-900">{blendedRoas}x</p>
|
||||
<p className="text-[11px] text-emerald-400 mt-1">
|
||||
{totalRevenue.toLocaleString('es-ES', { minimumFractionDigits: 2 })} € generados
|
||||
</p>
|
||||
|
||||
@@ -19,19 +19,19 @@ function FormattedMessage({ content }: { content: string }) {
|
||||
const matchStr = match[0];
|
||||
if (matchStr.startsWith('**') && matchStr.endsWith('**')) {
|
||||
parts.push(
|
||||
<strong key={match.index} className="font-bold text-white tracking-wide">
|
||||
<strong key={match.index} className="font-bold text-gray-900 tracking-wide">
|
||||
{matchStr.slice(2, -2)}
|
||||
</strong>
|
||||
);
|
||||
} else if (matchStr.startsWith('*') && matchStr.endsWith('*')) {
|
||||
parts.push(
|
||||
<em key={match.index} className="italic text-gray-300">
|
||||
<em key={match.index} className="italic text-gray-600">
|
||||
{matchStr.slice(1, -1)}
|
||||
</em>
|
||||
);
|
||||
} else if (matchStr.startsWith('`') && matchStr.endsWith('`')) {
|
||||
parts.push(
|
||||
<code key={match.index} className="bg-gray-900/80 text-brand-400 font-mono text-[11px] px-1.5 py-0.5 rounded border border-gray-700/60">
|
||||
<code key={match.index} className="bg-gray-50 text-brand-400 font-mono text-[11px] px-1.5 py-0.5 rounded border border-gray-200">
|
||||
{matchStr.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
@@ -85,7 +85,7 @@ function FormattedMessage({ content }: { content: string }) {
|
||||
|
||||
if (isQuote) {
|
||||
return (
|
||||
<blockquote key={lineIdx} className="border-l-2 border-brand-500/80 pl-3 italic text-gray-300 my-1 bg-gray-850/40 py-1 rounded-r">
|
||||
<blockquote key={lineIdx} className="border-l-2 border-brand-500/80 pl-3 italic text-gray-600 my-1 bg-gray-850/40 py-1 rounded-r">
|
||||
{renderInline(displayText)}
|
||||
</blockquote>
|
||||
);
|
||||
@@ -164,15 +164,15 @@ export default function AsistentePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-[calc(100vh-10rem)] bg-[#111827] border border-gray-800 rounded-xl overflow-hidden shadow-sm">
|
||||
<div className="flex flex-col h-[calc(100vh-10rem)] bg-white border border-gray-200 rounded-xl overflow-hidden shadow-sm">
|
||||
{/* Header */}
|
||||
<div className="p-4 border-b border-gray-800 flex items-center justify-between bg-[#0D121F]">
|
||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between bg-white">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-brand-500/20 text-brand-500 flex items-center justify-center">
|
||||
<Bot className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-sm text-white">Asistente IA Financiero & KPIs</h3>
|
||||
<h3 className="font-bold text-sm text-gray-900">Asistente IA Financiero & KPIs</h3>
|
||||
<p className="text-[11px] text-gray-400">Temperatura 0.0 · Verificación Numérica Estricta · Supabase RPC</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,7 +189,7 @@ export default function AsistentePage() {
|
||||
className={`max-w-2xl px-5 py-3.5 rounded-2xl ${
|
||||
m.role === 'user'
|
||||
? 'bg-gradient-to-r from-brand-500 to-amber-600 text-white font-medium shadow-md shadow-brand-500/10'
|
||||
: 'bg-[#1a2333] text-gray-200 border border-gray-800/80 shadow-lg shadow-black/20'
|
||||
: 'bg-[#1a2333] text-gray-800 border border-gray-200 shadow-lg shadow-black/20'
|
||||
}`}
|
||||
>
|
||||
<FormattedMessage content={m.content} />
|
||||
@@ -199,7 +199,7 @@ export default function AsistentePage() {
|
||||
|
||||
{loading && (
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-[#1a2333] text-gray-300 px-5 py-3 rounded-2xl text-sm border border-gray-800/80 flex items-center gap-2">
|
||||
<div className="bg-[#1a2333] text-gray-600 px-5 py-3 rounded-2xl text-sm border border-gray-200 flex items-center gap-2">
|
||||
<Sparkles className="w-4 h-4 animate-spin text-brand-500" />
|
||||
Calculando métricas sin alucinaciones...
|
||||
</div>
|
||||
@@ -209,7 +209,7 @@ export default function AsistentePage() {
|
||||
</div>
|
||||
|
||||
{/* Quick Prompts Chips */}
|
||||
<div className="px-6 py-2.5 bg-[#0D121F]/80 border-t border-gray-800/80 flex gap-2 overflow-x-auto text-xs text-gray-300">
|
||||
<div className="px-6 py-2.5 bg-white/80 border-t border-gray-200 flex gap-2 overflow-x-auto text-xs text-gray-600">
|
||||
<button
|
||||
onClick={() => sendMessage('¿Qué puedo preguntar?')}
|
||||
className="px-3 py-1.5 rounded-full bg-brand-500/20 text-brand-400 hover:bg-brand-500/30 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-brand-500/40"
|
||||
@@ -219,35 +219,35 @@ export default function AsistentePage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('¿Qué pasa si invierto 1.000 € en Meta?')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
|
||||
>
|
||||
<DollarSign className="w-3.5 h-3.5 text-brand-400" />
|
||||
🔮 Simular 1.000 € en Meta
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('¿Cómo va el cumplimiento de objetivos de este mes?')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
|
||||
>
|
||||
<Target className="w-3.5 h-3.5 text-emerald-400" />
|
||||
🎯 Objetivos de este mes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('Muéstrame el embudo comercial')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
|
||||
>
|
||||
<Filter className="w-3.5 h-3.5 text-blue-400" />
|
||||
🔄 Embudo comercial
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('¿Cuál es el CAC y ROAS por canal de los últimos 30 días?')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
|
||||
>
|
||||
<TrendingUp className="w-3.5 h-3.5 text-purple-400" />
|
||||
📊 CAC & ROAS por canal
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('Explícame la metodología 4Ventures y cómo calculáis el CAC de cohorte')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
|
||||
>
|
||||
<BookOpen className="w-3.5 h-3.5 text-amber-400" />
|
||||
📖 Metodología y atribución
|
||||
@@ -255,7 +255,7 @@ export default function AsistentePage() {
|
||||
</div>
|
||||
|
||||
{/* Input Box */}
|
||||
<div className="p-4 border-t border-gray-800 bg-[#0D121F]">
|
||||
<div className="p-4 border-t border-gray-200 bg-white">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -268,7 +268,7 @@ export default function AsistentePage() {
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Pregunta sobre inversión, metas, CAC, o escribe «¿Qué puedo preguntar?»..."
|
||||
className="flex-1 bg-[#111827] border border-gray-700 rounded-xl px-4 py-2.5 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 transition-all"
|
||||
className="flex-1 bg-white border border-gray-200 rounded-xl px-4 py-2.5 text-sm text-gray-900 placeholder-gray-500 focus:outline-none focus:border-brand-500 transition-all"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -74,7 +74,7 @@ function platformMeta(p: string) {
|
||||
function DualBadge({ dual }: { dual?: Dual }) {
|
||||
if (!dual) {
|
||||
return (
|
||||
<span className="text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full border border-gray-700 text-gray-500">
|
||||
<span className="text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full border border-gray-200 text-gray-500">
|
||||
Sin checks
|
||||
</span>
|
||||
);
|
||||
@@ -245,7 +245,7 @@ export default function CalendarClient({
|
||||
{calendarTitle} · privado (sin publicación pública) · doble check
|
||||
agencia/cliente
|
||||
</p>
|
||||
<div className="flex gap-1 rounded-lg border border-gray-800 p-0.5 bg-black/20">
|
||||
<div className="flex gap-1 rounded-lg border border-gray-200 p-0.5 bg-gray-50">
|
||||
{(
|
||||
[
|
||||
['month', 'Mes'],
|
||||
@@ -267,12 +267,12 @@ export default function CalendarClient({
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3.5">
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-4">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between text-gray-400 text-xs mb-1">
|
||||
<span>Total piezas</span>
|
||||
<CalendarDays className="w-4 h-4 text-brand-400" />
|
||||
</div>
|
||||
<p className="text-2xl font-extrabold text-white">{totalPosts}</p>
|
||||
<p className="text-2xl font-extrabold text-gray-900">{totalPosts}</p>
|
||||
</div>
|
||||
{platforms
|
||||
.filter((p) => p !== 'all')
|
||||
@@ -281,16 +281,16 @@ export default function CalendarClient({
|
||||
const meta = platformMeta(p);
|
||||
const n = initialPosts.filter((x) => x.platform === p).length;
|
||||
return (
|
||||
<div key={p} className="bg-[#111827] border border-gray-800 rounded-xl p-4">
|
||||
<div key={p} className="bg-white border border-gray-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between text-gray-400 text-xs mb-1">
|
||||
<span>{meta.label}</span>
|
||||
<meta.Icon className="w-4 h-4" />
|
||||
</div>
|
||||
<p className="text-2xl font-extrabold text-white">{n}</p>
|
||||
<p className="text-2xl font-extrabold text-gray-900">{n}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-4">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between text-gray-400 text-xs mb-1">
|
||||
<span>Doble OK</span>
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
|
||||
@@ -303,7 +303,7 @@ export default function CalendarClient({
|
||||
</div>
|
||||
|
||||
{/* Unified + per-network tabs */}
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl p-4 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-4 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 md:pb-0">
|
||||
{platforms.map((p) => {
|
||||
const count =
|
||||
@@ -324,7 +324,7 @@ export default function CalendarClient({
|
||||
: p === 'instagram'
|
||||
? 'bg-gradient-to-r from-pink-500 to-purple-600 text-white'
|
||||
: `${meta?.color || 'bg-gray-700'} text-white`
|
||||
: 'bg-gray-800/60 text-gray-300 hover:text-white'
|
||||
: 'bg-gray-100 text-gray-600 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{meta && <meta.Icon className="w-3.5 h-3.5" />}
|
||||
@@ -338,7 +338,7 @@ export default function CalendarClient({
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)}
|
||||
className="bg-gray-900 border border-gray-700 text-xs text-white rounded-lg px-2.5 py-1.5 focus:outline-none focus:border-brand-500"
|
||||
className="bg-gray-900 border border-gray-200 text-xs text-gray-900 rounded-lg px-2.5 py-1.5 focus:outline-none focus:border-brand-500"
|
||||
>
|
||||
<option value="all">Todos</option>
|
||||
<option value="approved">Aprobados</option>
|
||||
@@ -356,7 +356,7 @@ export default function CalendarClient({
|
||||
byDay.map(([day, posts]) => (
|
||||
<div
|
||||
key={day}
|
||||
className="rounded-xl border border-gray-800 bg-[#111827] p-4"
|
||||
className="rounded-xl border border-gray-200 bg-white p-4"
|
||||
>
|
||||
<p className="text-xs font-semibold text-brand-400 font-mono mb-3">
|
||||
{new Date(day).toLocaleDateString('es-ES', {
|
||||
@@ -400,10 +400,10 @@ export default function CalendarClient({
|
||||
|
||||
{selectedPost && (
|
||||
<div className="fixed inset-0 z-50 bg-black/75 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="bg-[#0D121F] border border-gray-800 rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden shadow-2xl flex flex-col">
|
||||
<div className="p-5 border-b border-gray-800 flex items-center justify-between bg-[#111827]">
|
||||
<div className="bg-white border border-gray-200 rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden shadow-2xl flex flex-col">
|
||||
<div className="p-5 border-b border-gray-200 flex items-center justify-between bg-white">
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-white">{selectedPost.title}</h3>
|
||||
<h3 className="text-base font-bold text-gray-900">{selectedPost.title}</h3>
|
||||
<p className="text-xs text-gray-400 font-mono mt-0.5">
|
||||
{selectedPost.platform} · {selectedPost.date} · {selectedPost.time}h
|
||||
</p>
|
||||
@@ -411,13 +411,13 @@ export default function CalendarClient({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPost(null)}
|
||||
className="text-gray-400 hover:text-white p-1 rounded-lg hover:bg-gray-800"
|
||||
className="text-gray-400 hover:text-gray-900 p-1 rounded-lg hover:bg-gray-50"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 overflow-y-auto space-y-4 text-sm text-gray-200">
|
||||
<div className="p-6 overflow-y-auto space-y-4 text-sm text-gray-800">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<DualBadge dual={dualMap[selectedPost.id]} />
|
||||
<span className="text-[10px] text-gray-500">
|
||||
@@ -437,8 +437,8 @@ export default function CalendarClient({
|
||||
</Block>
|
||||
|
||||
{canCollaborate && (
|
||||
<div className="rounded-xl border border-gray-800 bg-black/20 p-4 space-y-3">
|
||||
<div className="flex items-center gap-2 text-xs font-semibold text-white">
|
||||
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 space-y-3">
|
||||
<div className="flex items-center gap-2 text-xs font-semibold text-gray-900">
|
||||
<MessageSquare className="w-3.5 h-3.5 text-sky-400" />
|
||||
Comentarios
|
||||
</div>
|
||||
@@ -449,7 +449,7 @@ export default function CalendarClient({
|
||||
{comments.map((c) => (
|
||||
<li
|
||||
key={c.id}
|
||||
className="text-xs border border-gray-800 rounded-lg px-2.5 py-2"
|
||||
className="text-xs border border-gray-200 rounded-lg px-2.5 py-2"
|
||||
>
|
||||
<div className="flex justify-between gap-2 text-[10px] text-gray-500 mb-1">
|
||||
<span>
|
||||
@@ -465,7 +465,7 @@ export default function CalendarClient({
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-200 whitespace-pre-wrap">{c.body}</p>
|
||||
<p className="text-gray-800 whitespace-pre-wrap">{c.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -474,7 +474,7 @@ export default function CalendarClient({
|
||||
value={commentText}
|
||||
onChange={(e) => setCommentText(e.target.value)}
|
||||
placeholder="Escribe un comentario…"
|
||||
className="flex-1 rounded-lg bg-gray-900 border border-gray-700 px-3 py-2 text-xs text-white placeholder:text-gray-600 focus:outline-none focus:border-brand-500/50"
|
||||
className="flex-1 rounded-lg bg-gray-900 border border-gray-200 px-3 py-2 text-xs text-gray-900 placeholder:text-gray-600 focus:outline-none focus:border-brand-500/50"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -492,11 +492,11 @@ export default function CalendarClient({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-gray-800 bg-[#111827] flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="p-4 border-t border-gray-200 bg-white flex flex-wrap items-center justify-between gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyFullCopy(selectedPost)}
|
||||
className="px-3 py-2 rounded-xl bg-gray-800 hover:bg-gray-700 text-gray-200 text-xs font-semibold inline-flex items-center gap-1.5"
|
||||
className="px-3 py-2 rounded-xl bg-gray-100 hover:bg-gray-700 text-gray-800 text-xs font-semibold inline-flex items-center gap-1.5"
|
||||
>
|
||||
{copied ? <Check className="w-3.5 h-3.5 text-emerald-300" /> : <Copy className="w-3.5 h-3.5" />}
|
||||
Copiar copy
|
||||
@@ -508,7 +508,7 @@ export default function CalendarClient({
|
||||
onClick={() => approve(myApproved ? 'revoke' : 'approve')}
|
||||
className={`px-4 py-2 rounded-xl text-xs font-semibold inline-flex items-center gap-1.5 ${
|
||||
myApproved
|
||||
? 'bg-gray-800 text-gray-300 border border-gray-700'
|
||||
? 'bg-gray-100 text-gray-600 border border-gray-200'
|
||||
: 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/40'
|
||||
}`}
|
||||
>
|
||||
@@ -526,9 +526,9 @@ export default function CalendarClient({
|
||||
|
||||
function EmptyState() {
|
||||
return (
|
||||
<div className="col-span-full bg-[#111827] border border-gray-800 rounded-xl p-12 text-center text-gray-400">
|
||||
<div className="col-span-full bg-white border border-gray-200 rounded-xl p-12 text-center text-gray-400">
|
||||
<CalendarDays className="w-10 h-10 text-gray-600 mx-auto mb-3" />
|
||||
<p className="text-base font-semibold text-white">Sin piezas con este filtro.</p>
|
||||
<p className="text-base font-semibold text-gray-900">Sin piezas con este filtro.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -548,7 +548,7 @@ function Block({
|
||||
{label}
|
||||
</label>
|
||||
<div
|
||||
className={`p-3.5 rounded-xl bg-gray-900 border border-gray-800 whitespace-pre-wrap ${className}`}
|
||||
className={`p-3.5 rounded-xl bg-gray-900 border border-gray-200 whitespace-pre-wrap ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@@ -569,7 +569,7 @@ function PostCard({
|
||||
}) {
|
||||
const meta = platformMeta(post.platform);
|
||||
return (
|
||||
<div className="bg-[#0D121F] border border-gray-800 hover:border-gray-700 rounded-xl p-4 flex flex-col justify-between">
|
||||
<div className="bg-white border border-gray-200 hover:border-gray-200 rounded-xl p-4 flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2 gap-2">
|
||||
<span
|
||||
@@ -586,10 +586,10 @@ function PostCard({
|
||||
<p className="text-[11px] text-brand-400 font-mono mb-1">
|
||||
{post.time ? `${post.time}h` : '—'} · {post.format}
|
||||
</p>
|
||||
<h4 className="font-bold text-white text-sm mb-1.5 line-clamp-2">{post.title}</h4>
|
||||
<h4 className="font-bold text-gray-900 text-sm mb-1.5 line-clamp-2">{post.title}</h4>
|
||||
<p className="text-xs text-gray-400 line-clamp-2 italic">«{post.hook}»</p>
|
||||
</div>
|
||||
<div className="pt-3 mt-3 border-t border-gray-800/80 flex items-center justify-between">
|
||||
<div className="pt-3 mt-3 border-t border-gray-200 flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpen}
|
||||
@@ -600,7 +600,7 @@ function PostCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCopy}
|
||||
className="p-1.5 rounded-md hover:bg-gray-800 text-gray-400"
|
||||
className="p-1.5 rounded-md hover:bg-gray-50 text-gray-400"
|
||||
title="Copiar"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
|
||||
@@ -45,7 +45,7 @@ export default async function CalendarioPage({
|
||||
|
||||
if (ctx.portfolioWide) {
|
||||
return (
|
||||
<p className="text-sm text-gray-500 border border-gray-800 rounded-xl p-6 bg-[#111827]">
|
||||
<p className="text-sm text-gray-500 border border-gray-200 rounded-xl p-6 bg-white">
|
||||
Selecciona un cliente en Contexto para ver su hub RRSS (privado).
|
||||
</p>
|
||||
);
|
||||
@@ -107,7 +107,7 @@ export default async function CalendarioPage({
|
||||
<p className="text-[11px] font-bold tracking-[0.16em] uppercase text-sky-400 mb-2">
|
||||
RRSS · privado
|
||||
</p>
|
||||
<h2 className="text-3xl font-extrabold text-white tracking-tight">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900 tracking-tight">
|
||||
{ctx.label}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-400 mt-2 max-w-xl">
|
||||
@@ -118,13 +118,13 @@ export default async function CalendarioPage({
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link
|
||||
href="/dashboard/calendario?view=archive"
|
||||
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#16204A] text-white text-sm font-bold hover:bg-[#1a2a63]"
|
||||
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#16204A] text-gray-900 text-sm font-bold hover:bg-[#1a2a63]"
|
||||
>
|
||||
<Instagram className="w-4 h-4 mr-2" /> IG / FB
|
||||
</Link>
|
||||
<Link
|
||||
href="/dashboard/calendario?view=month&month=2026-10&tab=linkedin"
|
||||
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#2071CF] text-white text-sm font-bold hover:bg-[#1a5fb8]"
|
||||
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#2071CF] text-gray-900 text-sm font-bold hover:bg-[#1a5fb8]"
|
||||
>
|
||||
<Linkedin className="w-4 h-4 mr-2" /> LinkedIn
|
||||
</Link>
|
||||
|
||||
@@ -52,12 +52,12 @@ function ProgressBar({
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-300 font-medium">{label}</span>
|
||||
<span className="text-white font-semibold tabular-nums">
|
||||
<span className="text-gray-600 font-medium">{label}</span>
|
||||
<span className="text-gray-900 font-semibold tabular-nums">
|
||||
{p}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
|
||||
<div className="h-2 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className={`h-full ${bar}`} style={{ width: `${p}%` }} />
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 tabular-nums">
|
||||
@@ -195,7 +195,7 @@ export default async function EstrategiaPage() {
|
||||
) : (
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{/* Presupuesto */}
|
||||
<section className="bg-[#111827] border border-gray-800 rounded-xl p-6 space-y-5">
|
||||
<section className="bg-white border border-gray-200 rounded-xl p-6 space-y-5">
|
||||
<div className="flex items-center gap-2 text-amber-300">
|
||||
<Wallet className="w-4 h-4" />
|
||||
<h3 className="text-sm font-bold uppercase tracking-wider">
|
||||
@@ -214,22 +214,22 @@ export default async function EstrategiaPage() {
|
||||
tone="amber"
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded-lg border border-dashed border-gray-700 px-3 py-4 text-xs text-gray-500">
|
||||
<div className="rounded-lg border border-dashed border-gray-200 px-3 py-4 text-xs text-gray-500">
|
||||
Sin <code className="text-gray-400">spend_target</code> para este
|
||||
mes en <code className="text-gray-400">targets</code>. Gasto
|
||||
actual: {spendActual.toLocaleString('es-ES')} €.
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-3 pt-1">
|
||||
<div className="rounded-lg bg-black/25 border border-gray-800 px-3 py-2">
|
||||
<div className="rounded-lg bg-gray-50 border border-gray-200 px-3 py-2">
|
||||
<p className="text-[10px] uppercase text-gray-500">Ppto</p>
|
||||
<p className="text-lg font-bold text-white tabular-nums">
|
||||
<p className="text-lg font-bold text-gray-900 tabular-nums">
|
||||
{spendTarget.toLocaleString('es-ES')} €
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-black/25 border border-gray-800 px-3 py-2">
|
||||
<div className="rounded-lg bg-gray-50 border border-gray-200 px-3 py-2">
|
||||
<p className="text-[10px] uppercase text-gray-500">Gastado</p>
|
||||
<p className="text-lg font-bold text-white tabular-nums">
|
||||
<p className="text-lg font-bold text-gray-900 tabular-nums">
|
||||
{spendActual.toLocaleString('es-ES')} €
|
||||
</p>
|
||||
</div>
|
||||
@@ -237,7 +237,7 @@ export default async function EstrategiaPage() {
|
||||
</section>
|
||||
|
||||
{/* Consecución */}
|
||||
<section className="bg-[#111827] border border-gray-800 rounded-xl p-6 space-y-5">
|
||||
<section className="bg-white border border-gray-200 rounded-xl p-6 space-y-5">
|
||||
<div className="flex items-center gap-2 text-emerald-300">
|
||||
<Trophy className="w-4 h-4" />
|
||||
<h3 className="text-sm font-bold uppercase tracking-wider">
|
||||
|
||||
@@ -82,11 +82,11 @@ export default async function CanalDashPage({
|
||||
<div>
|
||||
<Link
|
||||
href="/dashboard/kpis"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-gray-400 hover:text-white mb-3"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-gray-400 hover:text-gray-900 mb-3"
|
||||
>
|
||||
<ArrowLeft className="w-3.5 h-3.5" /> Volver a métricas
|
||||
</Link>
|
||||
<h2 className="text-2xl font-bold text-white capitalize">
|
||||
<h2 className="text-2xl font-bold text-gray-900 capitalize">
|
||||
Canal · {channel}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
@@ -103,18 +103,18 @@ export default async function CanalDashPage({
|
||||
].map((s) => (
|
||||
<div
|
||||
key={s.label}
|
||||
className="rounded-xl border border-gray-800 bg-[#111827] px-4 py-3"
|
||||
className="rounded-xl border border-gray-200 bg-white px-4 py-3"
|
||||
>
|
||||
<p className="text-[10px] uppercase tracking-wider text-gray-500">
|
||||
{s.label}
|
||||
</p>
|
||||
<p className="text-xl font-bold text-white mt-1">{s.value}</p>
|
||||
<p className="text-xl font-bold text-gray-900 mt-1">{s.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-gray-800 bg-[#111827] p-5">
|
||||
<h3 className="text-sm font-semibold text-white mb-3">
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-5">
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-3">
|
||||
Semanas ({points.length})
|
||||
</h3>
|
||||
{points.length === 0 ? (
|
||||
@@ -124,10 +124,10 @@ export default async function CanalDashPage({
|
||||
{points.map((p) => (
|
||||
<li
|
||||
key={p.week}
|
||||
className="flex flex-wrap items-center justify-between gap-2 text-xs border border-gray-800 rounded-lg px-3 py-2"
|
||||
className="flex flex-wrap items-center justify-between gap-2 text-xs border border-gray-200 rounded-lg px-3 py-2"
|
||||
>
|
||||
<span className="font-mono text-brand-400">{p.week}</span>
|
||||
<span className="text-gray-300">
|
||||
<span className="text-gray-600">
|
||||
{formatEur(p.spend)} · {formatNum(p.leads)} leads · CPL{' '}
|
||||
{p.cpl ? `${formatNum(p.cpl)} €` : '—'}
|
||||
</span>
|
||||
|
||||
@@ -3,39 +3,34 @@
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #0b0f19;
|
||||
--foreground: #f3f4f6;
|
||||
--card: #111827;
|
||||
--sidebar: #0d121f;
|
||||
--border: rgba(255, 255, 255, 0.06);
|
||||
--background: #ffffff;
|
||||
--foreground: #111827;
|
||||
--card: #ffffff;
|
||||
--sidebar: #ffffff;
|
||||
--border: #e5e7eb;
|
||||
--brand: #e58a3c;
|
||||
--muted: #9ca3af;
|
||||
--muted: #6b7280;
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
|
||||
background-image:
|
||||
radial-gradient(ellipse 80% 50% at 10% -10%, rgba(229, 138, 60, 0.12), transparent 55%),
|
||||
radial-gradient(ellipse 60% 40% at 100% 0%, rgba(56, 189, 248, 0.06), transparent 50%);
|
||||
background-attachment: fixed;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Scrollbars sobrios */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #1f2937 transparent;
|
||||
scrollbar-color: #d1d5db transparent;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(229, 138, 60, 0.35);
|
||||
color: #fff;
|
||||
background: rgba(229, 138, 60, 0.25);
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
@@ -27,8 +27,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="es" className={`dark ${sans.variable}`}>
|
||||
<body className={`${sans.className} bg-background text-gray-100 min-h-screen antialiased`}>
|
||||
<html lang="es" className={sans.variable}>
|
||||
<body className={`${sans.className} bg-background text-gray-900 min-h-screen antialiased`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, Suspense } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { createClient } from '@/lib/supabase/client';
|
||||
import { Mail, Lock, Eye, EyeOff, ShieldCheck, ArrowRight, Loader2 } from 'lucide-react';
|
||||
@@ -39,20 +40,24 @@ function LoginForm() {
|
||||
router.refresh();
|
||||
}
|
||||
} catch (err: any) {
|
||||
setErrorMsg(err?.message || 'Error de conexión con el servidor de autenticación.');
|
||||
setErrorMsg(
|
||||
err?.message || 'Error de conexión con el servidor de autenticación.'
|
||||
);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-[#0D121F] border border-gray-800/80 rounded-2xl p-8 shadow-2xl shadow-black/50 backdrop-blur-sm">
|
||||
<div className="bg-white border border-gray-200 rounded-2xl p-8 shadow-lg shadow-gray-200/80">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-semibold text-white">Iniciar Sesión</h2>
|
||||
<p className="text-xs text-gray-400 mt-1">Introduce tus credenciales corporativas para acceder a tu portal.</p>
|
||||
<h2 className="text-lg font-semibold text-gray-900">Iniciar Sesión</h2>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Introduce tus credenciales corporativas para acceder a tu portal.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{errorMsg && (
|
||||
<div className="mb-6 p-3.5 rounded-xl bg-red-500/10 border border-red-500/30 text-red-400 text-xs flex items-center gap-2">
|
||||
<div className="mb-6 p-3.5 rounded-xl bg-red-50 border border-red-200 text-red-600 text-xs flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-red-500 shrink-0" />
|
||||
<span>{errorMsg}</span>
|
||||
</div>
|
||||
@@ -60,7 +65,7 @@ function LoginForm() {
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-300 mb-1.5">
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1.5">
|
||||
Correo Electrónico
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -73,13 +78,13 @@ function LoginForm() {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="ejemplo@cliente.com"
|
||||
className="w-full pl-10 pr-4 py-2.5 bg-gray-900/80 border border-gray-800 rounded-xl text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
|
||||
className="w-full pl-10 pr-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-300 mb-1.5">
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1.5">
|
||||
Contraseña
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -92,14 +97,18 @@ function LoginForm() {
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••••••"
|
||||
className="w-full pl-10 pr-10 py-2.5 bg-gray-900/80 border border-gray-800 rounded-xl text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
|
||||
className="w-full pl-10 pr-10 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute inset-y-0 right-0 pr-3.5 flex items-center text-gray-400 hover:text-gray-200 transition-colors"
|
||||
className="absolute inset-y-0 right-0 pr-3.5 flex items-center text-gray-400 hover:text-gray-700 transition-colors"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
{showPassword ? (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,33 +137,40 @@ function LoginForm() {
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0B0F19] text-gray-100 flex flex-col justify-center items-center px-4 relative overflow-hidden">
|
||||
{/* Background Glow */}
|
||||
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px] bg-gradient-to-tr from-brand-500/10 via-amber-500/5 to-purple-600/10 blur-[120px] rounded-full pointer-events-none" />
|
||||
<div className="min-h-screen bg-white text-gray-900 flex flex-col justify-center items-center px-4 relative overflow-hidden">
|
||||
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px] bg-gradient-to-tr from-brand-500/10 via-amber-500/5 to-orange-400/10 blur-[120px] rounded-full pointer-events-none" />
|
||||
|
||||
<div className="w-full max-w-md relative z-10">
|
||||
{/* Header Branding */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-brand-500 to-amber-600 text-white font-bold text-xl shadow-xl shadow-brand-500/20 mb-4 border border-brand-400/30">
|
||||
4V
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">4VENTURES</h1>
|
||||
<p className="text-sm text-gray-400 mt-1 font-medium">Centro de Mando del Cliente</p>
|
||||
<Image
|
||||
src="/logo-4ventures.png"
|
||||
alt="4Ventures"
|
||||
width={72}
|
||||
height={72}
|
||||
className="mx-auto rounded-2xl shadow-lg shadow-brand-500/15 mb-4"
|
||||
priority
|
||||
/>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-gray-900">
|
||||
4VENTURES
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 mt-1 font-medium">
|
||||
Centro de Mando del Cliente
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Login Box wrapped in Suspense for useSearchParams */}
|
||||
<Suspense fallback={
|
||||
<div className="bg-[#0D121F] border border-gray-800/80 rounded-2xl p-8 flex justify-center items-center h-64">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-brand-500" />
|
||||
</div>
|
||||
}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="bg-white border border-gray-200 rounded-2xl p-8 flex justify-center items-center h-64">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-brand-500" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<LoginForm />
|
||||
</Suspense>
|
||||
|
||||
{/* Security Footer */}
|
||||
<div className="mt-8 flex items-center justify-center gap-4 text-[11px] text-gray-500">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
|
||||
<ShieldCheck className="w-3.5 h-3.5 text-emerald-500" />
|
||||
<span>Aislamiento RLS multi-tenant</span>
|
||||
</div>
|
||||
<span>·</span>
|
||||
|
||||
Reference in New Issue
Block a user