"use client"; import { CheckIcon, CirclePauseIcon, ClockIcon, GaugeIcon, SparklesIcon } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; export interface LimitReachedUpgrade { plan: string; limit: number; } export interface LimitReachedProps { used?: number; limit?: number; unit?: string; shortUnit?: string; period?: string; planName?: string; secondsUntilReset?: number; resetDate?: string; upgrade?: LimitReachedUpgrade | null; stillWorks?: string[]; paused?: string[]; upgrading?: boolean; secondaryLabel?: string; secondaryDoneLabel?: string; onUpgrade?: () => void; onSecondary?: () => void; onReset?: () => void; className?: string; } const meterSpring = { type: "spring", stiffness: 110, damping: 17 } as const; const roll = { type: "spring", stiffness: 320, damping: 32 } as const; const glide = { type: "spring", stiffness: 380, damping: 34 } as const; // Stays false until mounted so the server render and the first client render match. function useSafeReducedMotion() { const prefers = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted && !!prefers; } const format = (n: number) => n.toLocaleString("en-US"); const pad = (n: number) => String(n).padStart(2, "0"); function split(seconds: number) { const s = Math.max(0, Math.floor(seconds)); return { d: Math.floor(s / 86400), h: Math.floor((s % 86400) / 3600), m: Math.floor((s % 3600) / 60), s: s % 60 }; } function spoken(seconds: number) { const t = split(seconds); const parts = [ t.d && `${t.d} ${t.d === 1 ? "day" : "days"}`, t.h && `${t.h} ${t.h === 1 ? "hour" : "hours"}`, !t.d && t.m && `${t.m} min`, ].filter(Boolean); return parts.length ? parts.join(" ") : "less than a minute"; } function Roll({ text, reduce, className }: { text: string; reduce: boolean; className?: string }) { const chars = text.split(""); return ( {chars.map((char, i) => { const key = chars.length - i; if (!/\d/.test(char)) return {char}; return ( {char} {Array.from({ length: 10 }, (_, n) => ( {n} ))} ); })} ); } function useCountdown(start: number, onZero?: () => void) { const [left, setLeft] = useState(start); const onZeroRef = useRef(onZero); useEffect(() => { onZeroRef.current = onZero; }, [onZero]); useEffect(() => { setLeft(start); if (start <= 0) return; const began = Date.now(); const id = window.setInterval(() => { const next = Math.max(0, start - Math.floor((Date.now() - began) / 1000)); setLeft(next); if (next === 0) { window.clearInterval(id); onZeroRef.current?.(); } }, 250); return () => window.clearInterval(id); }, [start]); return left; } export function LimitReached({ used = 500, limit = 500, unit = "AI runs", shortUnit = "runs", period = "this month", planName = "Team", secondsUntilReset = 3 * 86400 + 4 * 3600 + 12 * 60 + 8, resetDate = "Nov 1", upgrade = { plan: "Pro", limit: 2000 }, stillWorks = ["Editing and comments", "Search across projects", "Exports and the API"], paused = ["AI summaries", "Auto-tagging uploads", "Ask Ridgeline"], upgrading = false, secondaryLabel = "Ask your admin", secondaryDoneLabel = "Admin notified", onUpgrade, onSecondary, onReset, className, }: LimitReachedProps) { const reduce = useSafeReducedMotion(); const left = useCountdown(secondsUntilReset, onReset); const [asked, setAsked] = useState(false); const atLimit = used >= limit; const fraction = limit > 0 ? Math.min(1, used / limit) : 1; const percent = Math.round(fraction * 100); const t = split(left); const canUpgrade = !!upgrade && upgrade.limit > limit; useEffect(() => { if (!atLimit) setAsked(false); }, [atLimit]); return (
{atLimit ? : }

{atLimit ? `You've used all ${format(limit)} ${unit} ${period}` : `${format(limit - used)} ${unit} left ${period}`}

{atLimit ? `AI features are paused until your ${planName} plan resets. Everything else works as usual.` : "Paused features are back on."}

{`${format(used)} of ${format(limit)} ${shortUnit} used`} {" of "} {shortUnit} {`${percent}%`}

{`Resets in ${spoken(left)}, on ${resetDate}`} Resets in d h m s · {resetDate}

{atLimit ? ( {canUpgrade && upgrade && ( )} ) : ( {`You're on ${planName} · ${format(limit)} ${unit} a month`} )}
); } function FeatureList({ title, items, on, reduce }: { title: string; items: string[]; on: boolean; reduce: boolean }) { return (

{title}

); }