All files / components UsageExhausted.tsx

100% Statements 49/49
100% Branches 5/5
100% Functions 2/2
100% Lines 49/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 501x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 6x 6x 6x 6x 6x 6x 6x 7x 7x 7x 7x 1x 7x 7x 7x 7x 7x 7x 7x  
import type { UsageInfo } from "../types/job";
 
/**
 * The FR-009 exhausted state: rendered whenever postJobAnalysis rejects with
 * a "usage-limit-reached" JobPanelError. Always a designed card — message,
 * concrete reset date, Upgrade action (free tier only) — never the generic
 * error banner (SC-003).
 */
export function UsageExhausted({
  usage,
  onUpgrade,
}: {
  usage: UsageInfo;
  onUpgrade: () => void;
}) {
  const tierLabel = usage.tier === "premium" ? "premium" : "free";
 
  return (
    <div
      role="status"
      aria-label="Monthly analysis allowance used"
      className="flex h-full min-h-48 flex-col items-center justify-center gap-2 rounded-xl border border-gray-200/70 bg-white p-6 text-center dark:border-gray-800 dark:bg-gray-900"
    >
      <h3 className="text-sm font-semibold text-gray-800 dark:text-gray-100">
        You&rsquo;ve used all {usage.limit} {tierLabel} analyses this month
      </h3>
      <p className="text-sm text-gray-500 dark:text-gray-400">
        Your allowance resets on {formatResetDate(usage.resetsAt)}.
      </p>
      {usage.tier === "free" && (
        <button
          onClick={onUpgrade}
          aria-label="Upgrade to Premium"
          className="mt-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-700"
        >
          Upgrade to Premium
        </button>
      )}
    </div>
  );
}
 
function formatResetDate(iso: string): string {
  return new Date(iso).toLocaleDateString("en-US", {
    month: "long",
    day: "numeric",
    timeZone: "UTC",
  });
}