Landing del cliente: galería apaisada con lightbox y quita "Entrar"

La galería de trabajos (GaleriaTrabajos) en la landing personalizada del cliente
pasa a formato apaisado (3/2) y, al pulsar una foto, se amplía en un lightbox con
navegación ‹ ›, Esc y clic fuera para cerrar. Se quita el botón "Entrar" del
header de esa landing (TenantBrand sin showLogin): el cliente final no entra al
panel.

Revierte el cambio anterior en public/b2b.html (era la landing equivocada).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-06-11 17:40:12 +02:00
parent 5afda5af05
commit facf3cd79f
3 changed files with 116 additions and 135 deletions

View File

@@ -1,3 +1,6 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import type { PublicGaleriaFoto } from '@/lib/funnel/public-queries';
type GaleriaTrabajosProps = {
@@ -5,11 +8,37 @@ type GaleriaTrabajosProps = {
nombreEmpresa: string;
};
// Galería de trabajos del reformista en su landing pública. Solo se muestra si
// el reformista ha subido fotos desde su panel.
// Galería de trabajos del reformista en su landing pública. Solo se muestra si el reformista ha
// subido fotos desde su panel. Formato apaisado y, al pulsar una foto, se amplía en un lightbox
// con navegación entre todas las imágenes.
export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajosProps) {
const [idx, setIdx] = useState<number | null>(null);
const cerrar = useCallback(() => setIdx(null), []);
const mover = useCallback(
(d: number) => setIdx((cur) => (cur === null ? cur : (cur + d + fotos.length) % fotos.length)),
[fotos.length],
);
useEffect(() => {
if (idx === null) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') cerrar();
else if (e.key === 'ArrowRight') mover(1);
else if (e.key === 'ArrowLeft') mover(-1);
};
document.addEventListener('keydown', onKey);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = '';
};
}, [idx, cerrar, mover]);
if (fotos.length === 0) return null;
const actual = idx !== null ? fotos[idx] : null;
return (
<section id="galeria" className="bg-gray-50 section" aria-label="Galería de trabajos">
<div className="container">
@@ -24,24 +53,31 @@ export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajo
Reformas que ya hemos hecho
</h2>
<p className="text-gray-500 mt-3 leading-relaxed">
Una muestra real del trabajo de {nombreEmpresa}. Calidad de acabados, plazos cumplidos.
Una muestra real del trabajo de {nombreEmpresa}. Toca cualquier imagen para verla en grande.
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 md:gap-4">
{fotos.map((f) => (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-4">
{fotos.map((f, i) => (
<figure
key={f.id}
className="group relative aspect-[4/3] overflow-hidden rounded-xl bg-gray-100 border border-gray-200"
className="group relative aspect-[3/2] overflow-hidden rounded-xl bg-gray-100 border border-gray-200"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={f.url}
alt={f.titulo ?? `Reforma de ${nombreEmpresa}`}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<button
type="button"
onClick={() => setIdx(i)}
className="block h-full w-full cursor-zoom-in"
aria-label={`Ampliar ${f.titulo ?? `reforma de ${nombreEmpresa}`}`}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={f.url}
alt={f.titulo ?? `Reforma de ${nombreEmpresa}`}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
</button>
{f.titulo && (
<figcaption className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-3 text-white text-sm font-semibold opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<figcaption className="pointer-events-none absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-3 text-white text-sm font-semibold opacity-0 group-hover:opacity-100 transition-opacity duration-300">
{f.titulo}
</figcaption>
)}
@@ -49,6 +85,67 @@ export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajo
))}
</div>
</div>
{actual && (
<div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 p-4 sm:p-8"
onClick={cerrar}
role="dialog"
aria-modal="true"
aria-label="Imagen ampliada"
>
<button
type="button"
onClick={cerrar}
aria-label="Cerrar"
className="absolute right-4 top-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/15 text-2xl leading-none text-white hover:bg-white/30"
>
×
</button>
{fotos.length > 1 && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
mover(-1);
}}
aria-label="Anterior"
className="absolute left-3 top-1/2 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full bg-white/15 text-3xl leading-none text-white hover:bg-white/30 sm:left-6"
>
</button>
)}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={actual.url}
alt={actual.titulo ?? `Reforma de ${nombreEmpresa}`}
onClick={(e) => e.stopPropagation()}
className="max-h-[86vh] max-w-[94vw] w-auto rounded-lg shadow-2xl"
/>
{fotos.length > 1 && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
mover(1);
}}
aria-label="Siguiente"
className="absolute right-3 top-1/2 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full bg-white/15 text-3xl leading-none text-white hover:bg-white/30 sm:right-6"
>
</button>
)}
{actual.titulo && (
<div className="pointer-events-none absolute inset-x-0 bottom-5 text-center text-sm font-medium text-white/85">
{actual.titulo}
</div>
)}
</div>
)}
</section>
);
}