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>
152 lines
5.5 KiB
TypeScript
152 lines
5.5 KiB
TypeScript
'use client';
|
||
|
||
import { useCallback, useEffect, useState } from 'react';
|
||
import type { PublicGaleriaFoto } from '@/lib/funnel/public-queries';
|
||
|
||
type GaleriaTrabajosProps = {
|
||
fotos: PublicGaleriaFoto[];
|
||
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. 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">
|
||
<div className="max-w-2xl mb-10 md:mb-14">
|
||
<span
|
||
className="badge mb-4"
|
||
style={{ backgroundColor: 'var(--brand)', color: 'var(--brand-contrast)' }}
|
||
>
|
||
Nuestros trabajos
|
||
</span>
|
||
<h2 className="text-[clamp(1.75rem,4vw,2.75rem)] font-black tracking-tight text-black leading-tight">
|
||
Reformas que ya hemos hecho
|
||
</h2>
|
||
<p className="text-gray-500 mt-3 leading-relaxed">
|
||
Una muestra real del trabajo de {nombreEmpresa}. Toca cualquier imagen para verla en grande.
|
||
</p>
|
||
</div>
|
||
|
||
<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-[3/2] overflow-hidden rounded-xl bg-gray-100 border border-gray-200"
|
||
>
|
||
<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="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>
|
||
)}
|
||
</figure>
|
||
))}
|
||
</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>
|
||
);
|
||
}
|