← back to Govarbitrage
src/lib/utils.ts
51 lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/** shadcn/ui-style className combiner. */
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/** Round to whole cents (avoids float drift; all engine money is in dollars). */
export function round2(n: number): number {
return Math.round((n + Number.EPSILON) * 100) / 100;
}
export function clamp(n: number, lo: number, hi: number): number {
return Math.min(hi, Math.max(lo, n));
}
const usd = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
const usdCents = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
export function formatMoney(n: number | null | undefined, cents = false): string {
if (n === null || n === undefined || Number.isNaN(n)) return "—";
return cents ? usdCents.format(n) : usd.format(n);
}
export function formatPercent(ratio: number | null | undefined, digits = 0): string {
if (ratio === null || ratio === undefined || Number.isNaN(ratio)) return "—";
return `${(ratio * 100).toFixed(digits)}%`;
}
/** Human countdown to a future date, e.g. "2d 4h" or "Closed". */
export function countdown(to: Date | string | null | undefined, now = new Date()): string {
if (!to) return "—";
const target = typeof to === "string" ? new Date(to) : to;
const ms = target.getTime() - now.getTime();
if (ms <= 0) return "Closed";
const d = Math.floor(ms / 86_400_000);
const h = Math.floor((ms % 86_400_000) / 3_600_000);
const m = Math.floor((ms % 3_600_000) / 60_000);
if (d > 0) return `${d}d ${h}h`;
if (h > 0) return `${h}h ${m}m`;
return `${m}m`;
}