Web InnoventixFreeCode

Empty Success

Original · free

success / done empty state

byWeb InnoventixReact + Tailwind
emptysuccessstates
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/empty-success.json
empty-success.tsx
"use client";

import { useCallback, useEffect, useId, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";

type StepStatus = "passed" | "skipped";

type Step = {
  id: string;
  label: string;
  detail: string;
  duration: string;
  status: StepStatus;
};

const RUN_ID = "rel_8f42c1";

const STEPS: Step[] = [
  {
    id: "st1",
    label: "Bundle and type-check",
    detail: "1,204 modules, no new type errors",
    duration: "42s",
    status: "passed",
  },
  {
    id: "st2",
    label: "Unit and integration suites",
    detail: "318 tests, 0 failures, 2 quarantined",
    duration: "1m 09s",
    status: "passed",
  },
  {
    id: "st3",
    label: "Visual diff against baseline",
    detail: "94 snapshots, 3 intentional changes approved by Priya",
    duration: "27s",
    status: "passed",
  },
  {
    id: "st4",
    label: "Canary to 5% of traffic",
    detail: "Error rate held at 0.02%, p95 latency 214ms",
    duration: "4m 00s",
    status: "passed",
  },
  {
    id: "st5",
    label: "Full rollout to 12 regions",
    detail: "Frankfurt and São Paulo drained last, both healthy",
    duration: "1m 51s",
    status: "passed",
  },
  {
    id: "st6",
    label: "Database migration",
    detail: "No pending migrations in this release",
    duration: "0s",
    status: "skipped",
  },
];

const STATS: { id: string; value: string; label: string }[] = [
  { id: "k1", value: "5 / 5", label: "Checks passed" },
  { id: "k2", value: "8m 09s", label: "Pipeline time" },
  { id: "k3", value: "0", label: "Rollbacks today" },
];

const ROLLBACK_WINDOW_SECONDS = 30;

export default function EmptySuccess() {
  const reduceMotion = useReducedMotion();
  const panelId = useId();
  const detailsId = `${panelId}-details`;
  const statusId = `${panelId}-status`;

  const [rolledBack, setRolledBack] = useState(false);
  const [detailsOpen, setDetailsOpen] = useState(false);
  const [copied, setCopied] = useState(false);
  const [secondsLeft, setSecondsLeft] = useState(ROLLBACK_WINDOW_SECONDS);
  const [announcement, setAnnouncement] = useState("");
  const copyTimer = useRef<number | null>(null);

  useEffect(() => {
    if (rolledBack || secondsLeft <= 0) return;
    const tick = window.setInterval(() => {
      setSecondsLeft((current) => (current <= 1 ? 0 : current - 1));
    }, 1000);
    return () => window.clearInterval(tick);
  }, [rolledBack, secondsLeft]);

  useEffect(() => {
    return () => {
      if (copyTimer.current !== null) window.clearTimeout(copyTimer.current);
    };
  }, []);

  const handleCopy = useCallback(async () => {
    try {
      await navigator.clipboard.writeText(RUN_ID);
      setCopied(true);
      setAnnouncement(`Release ID ${RUN_ID} copied to clipboard.`);
    } catch {
      setCopied(false);
      setAnnouncement("Copy failed. Select the release ID manually.");
    }
    if (copyTimer.current !== null) window.clearTimeout(copyTimer.current);
    copyTimer.current = window.setTimeout(() => setCopied(false), 2000);
  }, []);

  const handleRollback = useCallback(() => {
    setRolledBack(true);
    setDetailsOpen(false);
    setAnnouncement("Rolled back to version 4.7.2. Traffic is serving the previous build.");
  }, []);

  const handleRedeploy = useCallback(() => {
    setRolledBack(false);
    setSecondsLeft(ROLLBACK_WINDOW_SECONDS);
    setAnnouncement(`Release ${RUN_ID} is live again across 12 regions.`);
  }, []);

  const windowOpen = !rolledBack && secondsLeft > 0;
  const progress = Math.max(0, Math.min(1, secondsLeft / ROLLBACK_WINDOW_SECONDS));

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-6 py-24 text-slate-900 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes esucc-ring {
          0% { transform: scale(0.82); opacity: 0.55; }
          70% { transform: scale(1.35); opacity: 0; }
          100% { transform: scale(1.35); opacity: 0; }
        }
        @keyframes esucc-draw {
          from { stroke-dashoffset: 34; }
          to { stroke-dashoffset: 0; }
        }
        @keyframes esucc-drift {
          0%, 100% { transform: translate3d(0, 0, 0); }
          50% { transform: translate3d(0, -8px, 0); }
        }
        .esucc-ring {
          animation: esucc-ring 2.6s cubic-bezier(0.22, 1, 0.36, 1) infinite;
        }
        .esucc-check {
          stroke-dasharray: 34;
          stroke-dashoffset: 34;
          animation: esucc-draw 700ms cubic-bezier(0.65, 0, 0.35, 1) 260ms forwards;
        }
        .esucc-drift {
          animation: esucc-drift 6s ease-in-out infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .esucc-ring, .esucc-drift { animation: none; }
          .esucc-check { animation: none; stroke-dashoffset: 0; }
        }
      `}</style>

      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-x-0 top-0 mx-auto h-72 max-w-3xl bg-[radial-gradient(60%_100%_at_50%_0%,rgba(16,185,129,0.18),transparent_70%)] dark:bg-[radial-gradient(60%_100%_at_50%_0%,rgba(16,185,129,0.16),transparent_70%)]"
      />

      <div className="relative mx-auto w-full max-w-3xl">
        <motion.div
          initial={reduceMotion ? false : { opacity: 0, y: 18 }}
          whileInView={reduceMotion ? undefined : { opacity: 1, y: 0 }}
          viewport={{ once: true, amount: 0.3 }}
          transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
          className="rounded-3xl border border-slate-200 bg-white p-8 shadow-[0_1px_2px_rgba(15,23,42,0.04),0_24px_60px_-30px_rgba(15,23,42,0.35)] sm:p-12 dark:border-slate-800 dark:bg-slate-900 dark:shadow-[0_24px_60px_-30px_rgba(0,0,0,0.9)]"
        >
          <div className="flex flex-col items-center text-center">
            <div className="esucc-drift relative flex h-20 w-20 items-center justify-center">
              <span
                aria-hidden="true"
                className={`absolute inset-0 rounded-full ${
                  rolledBack ? "bg-amber-400/30" : "bg-emerald-400/30"
                } ${rolledBack ? "" : "esucc-ring"}`}
              />
              <span
                aria-hidden="true"
                className={`absolute inset-0 rounded-full ring-1 ${
                  rolledBack
                    ? "bg-amber-50 ring-amber-200 dark:bg-amber-500/10 dark:ring-amber-500/30"
                    : "bg-emerald-50 ring-emerald-200 dark:bg-emerald-500/10 dark:ring-emerald-500/30"
                }`}
              />
              {rolledBack ? (
                <svg
                  aria-hidden="true"
                  viewBox="0 0 24 24"
                  fill="none"
                  className="relative h-9 w-9 text-amber-600 dark:text-amber-400"
                >
                  <path
                    d="M4 8h8.5a5.5 5.5 0 1 1 0 11H8"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                  <path
                    d="M7.5 4.5 4 8l3.5 3.5"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              ) : (
                <svg
                  aria-hidden="true"
                  viewBox="0 0 24 24"
                  fill="none"
                  className="relative h-9 w-9 text-emerald-600 dark:text-emerald-400"
                >
                  <path
                    className="esucc-check"
                    d="M5 12.5 10 17.5 19 7"
                    stroke="currentColor"
                    strokeWidth="2.2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              )}
            </div>

            <span
              className={`mt-6 inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs font-medium tracking-wide uppercase ${
                rolledBack
                  ? "bg-amber-50 text-amber-700 ring-1 ring-amber-200 dark:bg-amber-500/10 dark:text-amber-300 dark:ring-amber-500/25"
                  : "bg-emerald-50 text-emerald-700 ring-1 ring-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-300 dark:ring-emerald-500/25"
              }`}
            >
              <span
                aria-hidden="true"
                className={`h-1.5 w-1.5 rounded-full ${rolledBack ? "bg-amber-500" : "bg-emerald-500"}`}
              />
              {rolledBack ? "Serving v4.7.2" : "Queue empty"}
            </span>

            <h2 className="mt-4 text-2xl font-semibold tracking-tight text-balance sm:text-3xl">
              {rolledBack ? "Rolled back to v4.7.2" : "Everything shipped. Nothing left to do."}
            </h2>

            <p className="mt-3 max-w-xl text-sm leading-relaxed text-pretty text-slate-600 sm:text-base dark:text-slate-400">
              {rolledBack
                ? "All 12 regions are back on the previous build. The pipeline kept the artifacts, so you can put v4.8.0 back out whenever you're ready."
                : "Release v4.8.0 finished at 3:14 PM and is serving all 12 regions. Every check passed on the first attempt, so there's nothing in this queue that needs you."}
            </p>

            <div className="mt-5 flex flex-wrap items-center justify-center gap-2 text-xs">
              <span className="text-slate-500 dark:text-slate-500">Release ID</span>
              <button
                type="button"
                onClick={handleCopy}
                className="inline-flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-2.5 py-1.5 font-mono text-xs text-slate-700 transition-colors hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-300 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-900"
              >
                {RUN_ID}
                {copied ? (
                  <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-400">
                    <path d="M5 12.5 10 17.5 19 7" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
                  </svg>
                ) : (
                  <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" className="h-3.5 w-3.5">
                    <rect x="9" y="9" width="11" height="11" rx="2" stroke="currentColor" strokeWidth="1.8" />
                    <path d="M15 5H6a2 2 0 0 0-2 2v9" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
                  </svg>
                )}
                <span className="sr-only">{copied ? "Release ID copied" : "Copy release ID"}</span>
              </button>
            </div>
          </div>

          <dl className="mt-8 grid grid-cols-1 gap-3 sm:grid-cols-3">
            {STATS.map((stat) => (
              <div
                key={stat.id}
                className="rounded-2xl border border-slate-200 bg-slate-50/70 px-4 py-4 text-center dark:border-slate-800 dark:bg-slate-800/40"
              >
                <dt className="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-500">
                  {stat.label}
                </dt>
                <dd className="mt-1.5 font-mono text-lg font-semibold text-slate-900 tabular-nums dark:text-slate-100">
                  {stat.value}
                </dd>
              </div>
            ))}
          </dl>

          {windowOpen ? (
            <div className="mt-6 overflow-hidden rounded-2xl border border-amber-200 bg-amber-50/70 dark:border-amber-500/25 dark:bg-amber-500/[0.07]">
              <div className="flex flex-col gap-3 px-4 py-3.5 sm:flex-row sm:items-center sm:justify-between">
                <p className="text-sm text-amber-900 dark:text-amber-200">
                  Rollback window closes in{" "}
                  <span className="font-mono font-semibold tabular-nums">{secondsLeft}s</span>. After
                  that, v4.7.2 gets garbage collected.
                </p>
                <button
                  type="button"
                  onClick={handleRollback}
                  className="inline-flex shrink-0 items-center justify-center gap-1.5 rounded-lg border border-amber-300 bg-white px-3 py-1.5 text-sm font-medium text-amber-800 transition-colors hover:bg-amber-100 focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2 focus-visible:ring-offset-amber-50 focus-visible:outline-none dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200 dark:hover:bg-amber-500/20 dark:focus-visible:ring-offset-slate-900"
                >
                  <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" className="h-4 w-4">
                    <path d="M4 8h8.5a5.5 5.5 0 1 1 0 11H8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                    <path d="M7.5 4.5 4 8l3.5 3.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                  </svg>
                  Roll back
                </button>
              </div>
              <div
                role="progressbar"
                aria-label="Time remaining in rollback window"
                aria-valuemin={0}
                aria-valuemax={ROLLBACK_WINDOW_SECONDS}
                aria-valuenow={secondsLeft}
                aria-valuetext={`${secondsLeft} seconds remaining`}
                className="h-1 w-full bg-amber-200/60 dark:bg-amber-500/15"
              >
                <div
                  className="h-full origin-left bg-amber-500 transition-[width] duration-1000 ease-linear dark:bg-amber-400"
                  style={{ width: `${progress * 100}%` }}
                />
              </div>
            </div>
          ) : null}

          {!rolledBack ? (
            <div className="mt-6 rounded-2xl border border-slate-200 dark:border-slate-800">
              <h3>
                <button
                  type="button"
                  aria-expanded={detailsOpen}
                  aria-controls={detailsId}
                  onClick={() => setDetailsOpen((open) => !open)}
                  className="flex w-full items-center justify-between gap-3 rounded-2xl px-4 py-3.5 text-left text-sm font-medium text-slate-800 transition-colors hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-inset focus-visible:outline-none dark:text-slate-200 dark:hover:bg-slate-800/40"
                >
                  <span>What ran, and how long it took</span>
                  <svg
                    aria-hidden="true"
                    viewBox="0 0 24 24"
                    fill="none"
                    className={`h-4 w-4 shrink-0 text-slate-400 transition-transform duration-200 ${
                      detailsOpen ? "rotate-180" : "rotate-0"
                    }`}
                  >
                    <path d="m6 9 6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
                  </svg>
                </button>
              </h3>
              <div id={detailsId} hidden={!detailsOpen} className="border-t border-slate-200 px-4 py-2 dark:border-slate-800">
                <ul className="divide-y divide-slate-100 dark:divide-slate-800/70">
                  {STEPS.map((step) => (
                    <li key={step.id} className="flex items-start gap-3 py-3">
                      {step.status === "passed" ? (
                        <span
                          aria-hidden="true"
                          className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400"
                        >
                          <svg viewBox="0 0 24 24" fill="none" className="h-3 w-3">
                            <path d="M5 12.5 10 17.5 19 7" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
                          </svg>
                        </span>
                      ) : (
                        <span
                          aria-hidden="true"
                          className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-500"
                        >
                          <svg viewBox="0 0 24 24" fill="none" className="h-3 w-3">
                            <path d="M6 12h12" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
                          </svg>
                        </span>
                      )}
                      <div className="min-w-0 flex-1">
                        <p className="text-sm font-medium text-slate-800 dark:text-slate-200">
                          {step.label}
                          <span className="sr-only">{step.status === "passed" ? " — passed" : " — skipped"}</span>
                        </p>
                        <p className="mt-0.5 text-xs leading-relaxed text-slate-500 dark:text-slate-500">
                          {step.detail}
                        </p>
                      </div>
                      <span className="shrink-0 font-mono text-xs text-slate-400 tabular-nums dark:text-slate-500">
                        {step.duration}
                      </span>
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          ) : null}

          <div className="mt-8 flex flex-col gap-3 sm:flex-row sm:justify-center">
            {rolledBack ? (
              <button
                type="button"
                onClick={handleRedeploy}
                className="inline-flex items-center justify-center gap-2 rounded-xl bg-slate-900 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-slate-800 focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white dark:focus-visible:ring-slate-100 dark:focus-visible:ring-offset-slate-900"
              >
                <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" className="h-4 w-4">
                  <path d="M20 12a8 8 0 1 1-2.34-5.66" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
                  <path d="M20 4v4h-4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
                Re-deploy v4.8.0
              </button>
            ) : (
              <button
                type="button"
                className="inline-flex items-center justify-center gap-2 rounded-xl bg-slate-900 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-slate-800 focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white dark:focus-visible:ring-slate-100 dark:focus-visible:ring-offset-slate-900"
              >
                <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" className="h-4 w-4">
                  <path d="M6 4h9l5 5v11a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1Z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round" />
                  <path d="M14 4v6h6M9 14h6M9 17.5h4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
                Read the v4.8.0 notes
              </button>
            )}
            <button
              type="button"
              className="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:border-slate-700 dark:bg-transparent dark:text-slate-200 dark:hover:bg-slate-800/60 dark:focus-visible:ring-offset-slate-900"
            >
              <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" className="h-4 w-4">
                <rect x="3.5" y="5" width="17" height="15.5" rx="2" stroke="currentColor" strokeWidth="1.7" />
                <path d="M3.5 9.5h17M8 3.5V6M16 3.5V6M12 13v3.5h3" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              Schedule the next window
            </button>
          </div>

          <p className="mt-6 text-center text-xs text-slate-500 dark:text-slate-500">
            Deploys are frozen Fridays after 2:00 PM. The next open window is Monday at 9:00 AM.
          </p>

          <p id={statusId} aria-live="polite" className="sr-only">
            {announcement}
          </p>
        </motion.div>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →