Retour au catalogue

CTA Countdown

CTA avec compte a rebours integre pour creer l'urgence. Ideal pour les offres limitees et les lancements.

ctamedium Both Responsive a11y
bolddarksaasecommerceuniversalcentered
Theme
"use client";

import { motion } from "framer-motion";
import { useState, useEffect } from "react";
import { ArrowRight, Clock } from "lucide-react";

interface CtaCountdownProps {
  title?: string;
  description?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  /** Target date ISO string */
  targetDate?: string;
  badge?: string;
}

const EASE = [0.16, 1, 0.3, 1] as const;

function useCountdown(target: string) {
  const [remaining, setRemaining] = useState({ d: 0, h: 0, m: 0, s: 0 });

  useEffect(() => {
    function calc() {
      const diff = Math.max(0, new Date(target).getTime() - Date.now());
      setRemaining({
        d: Math.floor(diff / 86400000),
        h: Math.floor((diff % 86400000) / 3600000),
        m: Math.floor((diff % 3600000) / 60000),
        s: Math.floor((diff % 60000) / 1000),
      });
    }
    calc();
    const id = setInterval(calc, 1000);
    return () => clearInterval(id);
  }, [target]);

  return remaining;
}

const pad = (n: number) => String(n).padStart(2, "0");

export default function CtaCountdown({
  title = "Offre limitee dans le temps",
  description = "Profitez de -30% sur tous nos plans avant la fin de l'operation.",
  ctaLabel = "En profiter maintenant",
  ctaUrl = "#signup",
  targetDate = new Date(Date.now() + 3 * 86400000).toISOString(),
  badge = "Offre speciale",
}: CtaCountdownProps) {
  const { d, h, m, s } = useCountdown(targetDate);

  const units = [
    { label: "Jours", value: pad(d) },
    { label: "Heures", value: pad(h) },
    { label: "Min", value: pad(m) },
    { label: "Sec", value: pad(s) },
  ];

  return (
    <section
      style={{
        paddingTop: "var(--section-padding-y)",
        paddingBottom: "var(--section-padding-y)",
        background: "var(--color-background-dark)",
      }}
    >
      <div
        className="mx-auto"
        style={{
          maxWidth: "var(--container-max-width)",
          paddingLeft: "var(--container-padding-x)",
          paddingRight: "var(--container-padding-x)",
        }}
      >
        <motion.div
          initial={{ opacity: 0, y: 24 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.6, ease: EASE }}
          className="text-center max-w-2xl mx-auto"
        >
          {badge && (
            <span
              className="inline-flex items-center gap-2 mb-6 text-xs font-semibold uppercase tracking-widest px-4 py-1.5 rounded-full"
              style={{
                background: "var(--color-accent)",
                color: "var(--color-background)",
              }}
            >
              <Clock className="h-3.5 w-3.5" />
              {badge}
            </span>
          )}

          <h2
            className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight"
            style={{ color: "var(--color-foreground-on-dark)" }}
          >
            {title}
          </h2>

          <p
            className="mt-4 text-base leading-relaxed max-w-md mx-auto"
            style={{ color: "var(--color-foreground-on-dark)", opacity: 0.7 }}
          >
            {description}
          </p>

          {/* Countdown */}
          <div className="flex justify-center gap-4 mt-8">
            {units.map((u) => (
              <div key={u.label} className="text-center">
                <div
                  className="text-3xl md:text-4xl font-bold tabular-nums px-4 py-3 rounded-xl"
                  style={{
                    background: "var(--color-background-card)",
                    color: "var(--color-accent)",
                    minWidth: "72px",
                  }}
                >
                  {u.value}
                </div>
                <p
                  className="mt-1.5 text-[11px] uppercase tracking-wider"
                  style={{ color: "var(--color-foreground-on-dark)", opacity: 0.5 }}
                >
                  {u.label}
                </p>
              </div>
            ))}
          </div>

          <div className="mt-10">
            <a
              href={ctaUrl}
              className="inline-flex items-center gap-2 px-7 py-3.5 rounded-full text-sm font-semibold transition-transform hover:scale-105"
              style={{
                background: "var(--color-accent)",
                color: "var(--color-background)",
              }}
            >
              {ctaLabel}
              <ArrowRight className="h-4 w-4" />
            </a>
          </div>
        </motion.div>
      </div>
    </section>
  );
}

Reviews

CTA Countdown — React Cta Section — Incubator