"use client"; import { Battery, BatteryFull, BatteryLow, BatteryMedium, PlusCircle } from "lucide-react"; import Link from "next/link"; import { cn } from "@/lib/utils"; import { useTranslations } from "next-intl"; import { Button, Separator, Tooltip, TooltipContent, TooltipTrigger } from "../../../../shadcnui"; import { useCurrentUserContext } from "../../../user/contexts"; interface TokenStatusIndicatorProps { className?: string; size?: "sm" | "md" | "lg"; showExtraPages?: boolean; } /** * Credit balances are consumed fractionally server-side, so a raw balance reads * as noise in the UI ("999.48"). Every credit figure is shown as a whole number. * * Truncate rather than round: rounding 999.6 up to 1000 would advertise capacity * the company does not have. Truncation is used instead of Math.floor because a * balance can go negative (overdrawn extra credits), and floor(-1.09) is -2 — * which overstates the debt. trunc keeps it at -1 and matches floor for * non-negative values. */ const asWholeCredits = (value: number): string => Math.trunc(value).toLocaleString(); /** * TokenStatusIndicator displays the current status of available monthly and extra pages * using battery icons to represent the percentage of monthly pages remaining. * * Battery levels: * - BatteryFull: >75% available * - BatteryMedium: 25-75% available * - BatteryLow: 5-25% available * - Battery (empty): <5% available */ export function TokenStatusIndicator({ className, size = "md", showExtraPages = true }: TokenStatusIndicatorProps) { const { company } = useCurrentUserContext(); const t = useTranslations(); // Don't render if no company data if (!company) return null; const monthlyCredits = company.monthlyCredits; const availableMonthlyCredits = company.availableMonthlyCredits; const availableExtraCredits = company.availableExtraCredits; // Calculate percentage of available monthly pages const percentage = monthlyCredits > 0 ? (availableMonthlyCredits / monthlyCredits) * 100 : 0; const sizeClasses = { sm: "h-4 w-4", md: "h-5 w-5", lg: "h-6 w-6", }; const smallIconSizeClasses = { sm: "h-3 w-3", md: "h-4 w-4", lg: "h-5 w-5", }; const textSizeClasses = { sm: "text-xs", md: "text-sm", lg: "text-base", }; const iconSize = sizeClasses[size]; const smallIconSize = smallIconSizeClasses[size]; const textSize = textSizeClasses[size]; const getBatteryIcon = () => { if (percentage > 75) { return ; } else if (percentage > 50) { return ; } else if (percentage >= 25) { return ; } else { return ; } }; const getStatusColor = () => { if (percentage > 50) { return "text-success"; } else if (percentage >= 25) { return "text-warning"; } else { return "text-destructive"; } }; const tooltipContent = (
{t("billing.tokens.status", { defaultValue: "Page Status" })}
{t("billing.tokens.monthly", { defaultValue: "Monthly Pages" })}: {asWholeCredits(availableMonthlyCredits)} / {asWholeCredits(monthlyCredits)}
{t("billing.tokens.available", { defaultValue: "Available" })}: {percentage.toFixed(0)}%
{t("billing.tokens.available_extra", { defaultValue: "Extra Pages" })}: {asWholeCredits(availableExtraCredits)}
); return ( {/* The layout classes live on the trigger itself, which renders the