Rediseña panel y auth con la identidad de la landing B2C

- Vista de leads en tarjetas + tabla con toggle (tarjetas por defecto, preferencia persistida)
- Galería de trabajos: gestión en /panel/galeria y bloque público en el funnel
- Selector de tema por reformista (presets + color de marca opcional) aplicado a la landing
- Login y registro rediseñados a pantalla partida 50/50 con foto de reforma
- Enlace "Entrar" funcional en la cabecera del funnel; elimina Navbar muerto
- Unifica tipografía y botones del panel con los tokens de la landing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-06-01 13:51:00 +02:00
parent a91fe5ce2c
commit 1ea5d70675
30 changed files with 2797 additions and 283 deletions

View File

@@ -0,0 +1,73 @@
import { getGaleriaPanel } from '@/db/tenant-queries';
import { eliminarFotoGaleria } from './actions';
import { GALERIA_MAX_FOTOS } from '@/lib/galeria';
import GaleriaUploader from '@/components/panel/GaleriaUploader';
export const dynamic = 'force-dynamic';
export default async function GaleriaPage() {
const fotos = await getGaleriaPanel();
return (
<div className="space-y-8 max-w-3xl">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Galería de trabajos</h1>
<p className="text-sm text-gray-500 mt-1">
Sube fotos de reformas que ya has hecho. Aparecen en tu funnel para dar confianza al
cliente antes de pedir presupuesto.
</p>
</div>
<GaleriaUploader total={fotos.length} max={GALERIA_MAX_FOTOS} />
{fotos.length === 0 ? (
<p className="text-sm text-gray-400">
Aún no has subido ninguna foto. La galería no se mostrará en tu funnel hasta que añadas la
primera.
</p>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{fotos.map((foto) => (
<figure
key={foto.id}
className="group relative overflow-hidden rounded-xl border border-gray-200 bg-white"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={foto.url}
alt={foto.titulo ?? 'Reforma'}
className="aspect-[4/3] w-full object-cover"
/>
{foto.titulo && (
<figcaption className="px-3 py-2 text-xs font-medium text-gray-700 truncate">
{foto.titulo}
</figcaption>
)}
<form action={eliminarFotoGaleria.bind(null, foto.id)} className="absolute top-2 right-2">
<button
type="submit"
aria-label="Eliminar foto"
className="flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-gray-600 shadow-sm transition hover:bg-red-500 hover:text-white"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6" />
</svg>
</button>
</form>
</figure>
))}
</div>
)}
</div>
);
}