socios-comerciales.skyballo.../astro-partner-hub-main/src/components/PromocionFlashModal.tsx

156 lines
5.8 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { Check, Clock3, Copy, Gift, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
CUPON_PENDIENTE_KEY,
PROMOCION_OCULTA_KEY,
PROMOCION_VISTA_KEY,
fechaLocalHoy,
obtenerPromocionActiva,
type PromocionActiva,
} from "@/lib/promocionesService";
function tiempoRestante(fin: string) {
const diferencia = Math.max(0, new Date(fin).getTime() - Date.now());
const horas = Math.floor(diferencia / 3_600_000);
const minutos = Math.floor((diferencia % 3_600_000) / 60_000);
const segundos = Math.floor((diferencia % 60_000) / 1000);
return { horas, minutos, segundos };
}
export function PromocionFlashModal() {
const [promocion, setPromocion] = useState<PromocionActiva | null>(null);
const [abierta, setAbierta] = useState(false);
const [noMostrar, setNoMostrar] = useState(false);
const [copiado, setCopiado] = useState(false);
const [ahora, setAhora] = useState(Date.now());
useEffect(() => {
let vigente = true;
const hoy = fechaLocalHoy();
if (
window.localStorage.getItem(PROMOCION_OCULTA_KEY) === hoy ||
window.sessionStorage.getItem(PROMOCION_VISTA_KEY) === hoy
)
return;
obtenerPromocionActiva().then((resultado) => {
if (!vigente) return;
setPromocion(resultado);
setAbierta(true);
});
return () => {
vigente = false;
};
}, []);
useEffect(() => {
if (!abierta) return;
const intervalo = window.setInterval(() => setAhora(Date.now()), 1000);
return () => window.clearInterval(intervalo);
}, [abierta]);
const restante = useMemo(
() => (promocion ? tiempoRestante(promocion.terminaEn) : { horas: 0, minutos: 0, segundos: 0 }),
[promocion, ahora],
);
const cerrar = () => {
if (noMostrar) window.localStorage.setItem(PROMOCION_OCULTA_KEY, fechaLocalHoy());
window.sessionStorage.setItem(PROMOCION_VISTA_KEY, fechaLocalHoy());
setAbierta(false);
};
const copiar = async () => {
if (!promocion) return;
try {
await navigator.clipboard.writeText(promocion.codigo);
} catch {
// El código sigue visible para copiarlo manualmente si el navegador bloquea el portapapeles.
}
setCopiado(true);
window.setTimeout(() => setCopiado(false), 1600);
};
const aplicar = () => {
if (!promocion) return;
window.sessionStorage.setItem(CUPON_PENDIENTE_KEY, promocion.codigo);
cerrar();
window.location.assign("/reserva");
};
if (!abierta || !promocion) return null;
return (
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-ink/65 p-4 backdrop-blur-sm animate-in fade-in-0">
<section
role="dialog"
aria-modal="true"
aria-labelledby="titulo-promocion"
className="relative max-h-[90vh] w-[95vw] max-w-md overflow-y-auto rounded-3xl bg-card p-5 shadow-lift animate-in zoom-in-95 slide-in-from-bottom-4 duration-300 sm:p-6"
>
<Button
type="button"
variant="ghost"
size="icon"
onClick={cerrar}
aria-label="Cerrar promoción"
className="absolute right-3 top-3 z-10 rounded-xl"
>
<X />
</Button>
<div className="grid size-12 place-items-center rounded-2xl bg-brand text-brand-foreground">
<Gift className="size-6" />
</div>
<p className="mt-5 text-xs font-bold uppercase tracking-wide text-secondary">Oferta flash</p>
<h2 id="titulo-promocion" className="mt-1 break-words pr-8 text-xl font-bold leading-tight sm:text-2xl">
{promocion.titulo}
</h2>
<p className="mt-2 break-words text-sm text-muted-foreground">{promocion.descripcion}</p>
<div className="mt-5 rounded-2xl bg-muted p-4">
<p className="flex items-center justify-center gap-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
<Clock3 className="size-4" /> La oferta termina en
</p>
<div className="mt-3 grid grid-cols-3 gap-2" aria-live="polite">
{[
[restante.horas, "Horas"],
[restante.minutos, "Min"],
[restante.segundos, "Seg"],
].map(([valor, etiqueta]) => (
<div key={etiqueta} className="rounded-2xl bg-card px-2 py-3 text-center shadow-soft">
<p className="text-xl font-bold tabular-nums">{String(valor).padStart(2, "0")}</p>
<p className="text-[10px] uppercase tracking-wide text-muted-foreground">{etiqueta}</p>
</div>
))}
</div>
</div>
<div className="mt-4 grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2 rounded-2xl border border-dashed border-primary/40 bg-accent/50 p-3">
<div className="min-w-0">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">Tu código</p>
<p className="truncate font-mono text-lg font-bold text-primary">{promocion.codigo}</p>
</div>
<Button type="button" variant="outline" onClick={copiar} className="shrink-0 rounded-xl">
{copiado ? <Check /> : <Copy />}
{copiado ? "Copiado" : "Copiar código"}
</Button>
</div>
<Button type="button" size="lg" onClick={aplicar} className="mt-4 h-12 w-full rounded-2xl font-bold">
Aplicar cupón ahora
</Button>
<label className="mt-4 flex cursor-pointer items-center justify-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
checked={noMostrar}
onChange={(evento) => setNoMostrar(evento.target.checked)}
className="size-4 rounded border-border accent-primary"
/>
No volver a mostrar hoy
</label>
</section>
</div>
);
}